@gadmin2n/schematics 0.0.135 → 0.0.137

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.
Files changed (95) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +7 -6
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/gen-jwt.ts +85 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +1 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
  55. package/package.json +1 -1
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
@@ -1,587 +1,51 @@
1
- # Temporal Workflow Runtime
1
+ # temporal/
2
2
 
3
- 本目录包含 Workflow 执行引擎的所有组件。
3
+ **Phase 3 起本目录已废弃** — gadmin-test 不再内嵌 Temporal 服务。所有 workflow 执行
4
+ 走共享部署 `common/gadmin2/temporal-shared`。
4
5
 
5
- ## 架构
6
+ 设计文档:`docs/superpowers/specs/2026-07-06-shared-temporal-worker-design.md`
7
+ Phase 3 说明:`docs/superpowers/specs/2026-07-14-shared-temporal-phase3-design.md`
6
8
 
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
- └── worker/ # DSL 解释器 Worker
52
- ├── package.json
53
- ├── tsconfig.json
54
- ├── .env # Worker 环境变量
55
- └── src/
56
- ├── index.ts # Worker 启动入口
57
- ├── config.ts # 配置读取
58
- ├── outbox-poller.ts # 事件轮询器(扫描 outbox 表触发 workflow)
59
- ├── workflows/
60
- │ └── dsl-workflow.ts # 核心:DSL 动态解释器
61
- ├── activities/
62
- │ ├── index.ts
63
- │ ├── http-request.ts # HTTP 调用
64
- │ ├── db-query.ts # 数据库查询
65
- │ ├── db-execute.ts # 数据库写入
66
- │ ├── send-notification.ts # 多渠道通知
67
- │ ├── code-execute.ts # 沙箱 JS 执行
68
- │ └── reporting.ts # 状态回写到业务 DB
69
- └── dsl/
70
- ├── types.ts # DSL 类型定义
71
- ├── graph.ts # DAG 解析 + 拓扑排序
72
- ├── context.ts # 变量/上下文管理
73
- └── condition.ts # 条件表达式求值
74
- ```
75
-
76
- ## 快速启动
77
-
78
- ```bash
79
- cd temporal
80
-
81
- # 1. 启动 Temporal Server(复用项目现有 PostgreSQL)
82
- npm run up
83
-
84
- # 2. 安装 Worker 依赖
85
- npm run worker:install
86
-
87
- # 3. 启动 Worker(开发模式)
88
- npm run worker:dev
89
- ```
90
-
91
- ## 工作流程
92
-
93
- ```
94
- 1. 用户在前端点击 "Run" 按钮
95
- │
96
- ▼
97
- 2. NestJS 调用 temporalService.startWorkflow()
98
- │
99
- ▼
100
- 3. Temporal Server 收到请求,创建记录,放入 task queue
101
- │
102
- ▼
103
- 4. Worker 长轮询 Server,收到新任务
104
- │
105
- ▼
106
- 5. Worker 执行 dslWorkflow():
107
- - 解析 DSL JSON → DAG
108
- - 拓扑排序确定执行顺序
109
- - 逐节点执行对应 Activity
110
- - 条件节点做分支选择
111
- - 并行节点用 Promise.all
112
- - 审批节点等待 Signal
113
- │
114
- ▼
115
- 6. 每个节点执行结果回写到 t_workflow_node_execution
116
- │
117
- ▼
118
- 7. Workflow 完成/失败 → 更新 t_workflow_instance 状态
119
- ```
120
-
121
- ## 数据库表分工
122
-
123
- Temporal 体系下有 **两类 PG 表**,分布在不同 schema,互不干扰:
124
-
125
- | 类别 | 所在 schema | 谁来写 | 典型表 | 用途 |
126
- |------|------------|--------|--------|------|
127
- | **Temporal 系统表** | `temporal` (Server 启动时自动建表) | 仅 Temporal Server | `executions`、`history_node`、`history_tree`、`current_executions`、`task_queues`、`tasks`、`timer_tasks`、`shard`、`namespaces` | Workflow 状态机、事件历史、任务队列、定时器、分片协调 |
128
- | **业务表** | `public` (Prisma 管理) | NestJS / Worker Activity | `t_workflow`、`t_workflow_version`、`t_workflow_instance`、`t_workflow_node_execution`、`t_workflow_event_outbox` | 业务可见的工作流定义、实例、节点执行记录、事件触发 outbox |
129
-
130
- > Temporal Server **从不读写**业务表;业务代码(NestJS / Worker Activity)**从不读写** Temporal 系统表。两者通过 `temporal_run_id` 字段(保存在 `t_workflow_instance` 上)建立关联。
131
-
132
- ## 两种触发场景的时序图
133
-
134
- ### 场景 A:NestJS 主动调用 `temporalService.startWorkflow()`
135
-
136
- 入口:用户在前端点 "Run",或 Agenda 定时任务 / 其他业务代码主动调用 `WorkflowService.executeWorkflow()`。
137
-
138
- ```
139
- ┌────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌────────────────┐ ┌────────────┐
140
- │Frontend│ │ NestJS │ │ PostgreSQL │ │ Temporal Server│ │ Worker │
141
- │ │ │ WorkflowService │ │ (业务 schema) │ │ (gRPC :7233) │ │ (long-poll)│
142
- └───┬────┘ └────────┬─────────┘ └────────┬────────┘ └───────┬────────┘ └─────┬──────┘
143
- │ POST /run │ │ │ │
144
- ├────────────────►│ │ │ │
145
- │ │ SELECT t_workflow + t_workflow_version │ │
146
- │ ├─────────────────────►│ │ │
147
- │ │◄─────────────────────┤ 校验 PUBLISHED/enabled │
148
- │ │ │ │ │
149
- │ │ INSERT t_workflow_instance (status=PENDING)│ │
150
- │ ├─────────────────────►│ │ │
151
- │ │◄──── instanceId ─────┤ │ │
152
- │ │ │ │ │
153
- │ │ temporalService.startWorkflow('dslWorkflow', …) │
154
- │ ├──────────────────────────────────────────►│ │
155
- │ │ │ 写 temporal.executions / history_node │
156
- │ │ │ + 投递 WorkflowTask 到 task_queues │
157
- │ │ │◄───────────────────┤ │
158
- │ │◄──── temporalRunId ──────────────────────┤ │
159
- │ │ │ │ │
160
- │ │ UPDATE t_workflow_instance SET temporal_run_id│ │
161
- │ ├─────────────────────►│ │ │
162
- │ │ │ │ PollWorkflowTask │
163
- │ │ │ │◄──────────────────┤
164
- │ │ │ ├──── task ────────►│
165
- │ │ │ │ │ 执行 dslWorkflow()
166
- │ │ │ │ │ ──┐
167
- │ │ │ │ ScheduleActivity │ <─┘
168
- │ │ │ │◄──────────────────┤
169
- │ │ │ ├── activity task ─►│
170
- │ │ │ │ │ Activity 内调用
171
- │ │ │ │ │ reporting.ts:
172
- │ │ │◄────── INSERT/UPDATE t_workflow_node_execution ─┤
173
- │ │ │◄────── UPDATE t_workflow_instance (RUNNING/DONE)┤
174
- │ │ │ │ CompleteActivity │
175
- │ │ │ │◄──────────────────┤
176
- │ │ │ │ │
177
- │ │ │ │ … 直到 Workflow 完成 …
178
- │◄────── 200 OK { instanceId, temporalRunId } ────────────────┤ │
179
- ```
180
-
181
- **涉及的 PG 表:**
182
-
183
- | 表 | 操作 | 写入方 |
184
- |----|------|--------|
185
- | `t_workflow`、`t_workflow_version` | SELECT | NestJS(取 DSL + 校验状态) |
186
- | `t_workflow_instance` | INSERT (PENDING) → UPDATE (temporal_run_id) → UPDATE (status) | NestJS 创建;Worker `reporting.ts` 在执行中改 |
187
- | `t_workflow_node_execution` | INSERT/UPDATE 每个节点 | Worker `reporting.ts`(Activity 内调用) |
188
- | `temporal.executions` / `current_executions` | INSERT (新 WorkflowExecution) → UPDATE 状态 | Temporal Server(Frontend / History Service) |
189
- | `temporal.history_node` / `history_tree` | INSERT 事件(Started / ActivityScheduled / Completed …) | Temporal Server |
190
- | `temporal.task_queues` / `tasks` | INSERT/DELETE Workflow & Activity Task | Temporal Server(Matching Service) |
191
- | `temporal.timer_tasks` | 节点超时、Workflow 总超时、Sleep 节点 | Temporal Server |
9
+ 保留本目录(而非删除整个 `temporal/`)是为了让代码库树一眼看得到"这里以前有内嵌
10
+ temporal,现在已迁走",避免新同事/AI 找不到线索反复重建。
192
11
 
193
12
  ---
194
13
 
195
- ### 场景 B:Worker `outbox-poller` 因业务表变更触发
196
-
197
- 入口:业务表(如 `t_order`)发生 INSERT/UPDATE/DELETE → AFTER 触发器写一行到 `t_workflow_event_outbox` → Worker 进程内 outbox-poller 扫到。
198
-
199
- ```
200
- ┌────────────┐ ┌────────────────┐ ┌─────────────────────┐ ┌────────────────┐ ┌──────────────┐
201
- │ 业务来源 │ │ PostgreSQL │ │ Worker │ │ Temporal Server│ │ Worker │
202
- │ (任意 INSERT│ │ (业务 schema) │ │ outbox-poller.ts │ │ (gRPC :7233) │ │ dslWorkflow │
203
- │ /UPDATE) │ │ │ │ (Worker 进程内) │ │ │ │ (同一进程) │
204
- └─────┬──────┘ └───────┬────────┘ └──────────┬──────────┘ └───────┬────────┘ └──────┬───────┘
205
- │ INSERT/UPDATE t_order │ │ │
206
- ├─────────────────►│ │ │ │
207
- │ │ AFTER TRIGGER notify_workflow_event() │ │
208
- │ │ INSERT t_workflow_event_outbox (同事务) │ │
209
- │ │ │ │ │
210
- │ │ ◄── 每 10s ───────── │ UPDATE outbox │ │
211
- │ │ │ SET processed=true │ │
212
- │ │ │ ... FOR UPDATE SKIP LOCKED │
213
- │ ├──── 未处理事件批 ────►│ │ │
214
- │ │ │ │ │
215
- │ │ SELECT t_workflow JOIN t_workflow_version │ │
216
- │ │ WHERE status='PUBLISHED' AND is_enabled │ │
217
- │ ├──────────────────────►│ matchEventName + filterExpression │
218
- │ │ │ │ │
219
- │ │ INSERT t_workflow_instance (creator='event_trigger') │
220
- │ │◄──────────────────────┤ │ │
221
- │ ├──── instanceId ──────►│ │ │
222
- │ │ │ temporalClient.workflow.start('dslWorkflow', …)
223
- │ │ ├─────────────────────►│ │
224
- │ │ │ │ 写 temporal.executions
225
- │ │ │ │ + 投递 WorkflowTask│
226
- │ │ │◄────── runId ────────┤ │
227
- │ │ UPDATE t_workflow_instance SET temporal_run_id│ │
228
- │ │◄──────────────────────┤ │ │
229
- │ │ │ │ PollWorkflowTask │
230
- │ │ │ │◄──────────────────┤
231
- │ │ │ ├──── task ────────►│
232
- │ │ │ │ │ 执行节点 + Activity
233
- │ │◄──────── INSERT/UPDATE t_workflow_node_execution ─────────────────┤
234
- │ │◄──────── UPDATE t_workflow_instance (status, ended_at) ──────────┤
235
- │ │ │ │ │
236
- │ │ (失败时) UPDATE t_workflow_event_outbox │ │
237
- │ │ SET processed=false, retry_count=n │ │
238
- │ │◄──────────────────────┤ │ │
239
- ```
240
-
241
- **涉及的 PG 表:**
242
-
243
- | 表 | 操作 | 写入方 |
244
- |----|------|--------|
245
- | 业务表(如 `t_order`) | 任意 DML | 业务来源 |
246
- | `t_workflow_event_outbox` | INSERT(触发器,业务事务内)→ UPDATE processed/retry_count(poller) | DB Trigger + outbox-poller |
247
- | `t_workflow`、`t_workflow_version` | SELECT 已发布且启用的最新版本 | outbox-poller |
248
- | `t_workflow_instance` | INSERT (PENDING, creator='event_trigger') → UPDATE temporal_run_id → UPDATE status | outbox-poller 创建;Worker `reporting.ts` 在执行中改 |
249
- | `t_workflow_node_execution` | INSERT/UPDATE 每个节点 | Worker `reporting.ts` |
250
- | `temporal.executions` / `history_node` / `task_queues` / `tasks` / `timer_tasks` | 同场景 A | Temporal Server |
251
-
252
- ---
253
-
254
- ---
255
-
256
- ### 场景 C:Temporal Schedule API(定时任务触发,`cron_trigger`)
257
-
258
- > ✅ **当前状态**:已实现。`WorkflowService.publish()` 解析 DSL 中的 `cron_trigger` 节点,调 `TemporalService.upsertCronSchedule()` 注册 Temporal Schedule;`toggleEnabled` / `remove` 联动 pause/unpause/delete schedule。Worker 新增 `cronTriggerWorkflow`,由 Schedule 触发后创建 `t_workflow_instance(creator='cron')` 并以子工作流方式执行 `dslWorkflow`。
259
-
260
- 入口:Workflow 中包含 `cron_trigger` 节点(带 `cronExpression` 配置),点击 Publish 时由 NestJS 把 cron 表达式注册到 Temporal Server 的 Schedule 子系统;之后由 **Server 自己**按时间触发,无需 NestJS / Worker 长跑组件。
261
-
262
- ```
263
- ┌────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌──────────────────────────┐ ┌────────────┐
264
- │Frontend│ │ NestJS │ │ PostgreSQL │ │ Temporal Server │ │ Worker │
265
- │ │ │ WorkflowService │ │ (业务 schema) │ │ (Schedule + Workflow) │ │ (long-poll)│
266
- └───┬────┘ └────────┬─────────┘ └────────┬────────┘ └───────┬──────────────────┘ └─────┬──────┘
267
- │ POST /:id/publish│ │ │ │
268
- ├────────────────►│ │ │ │
269
- │ │ 解析 DSL:找 cron_trigger 节点 │ │
270
- │ │ UPDATE t_workflow SET status='PUBLISHED' │ │
271
- │ ├─────────────────────►│ │ │
272
- │ │ │ │ │
273
- │ │ client.schedule.create({ │ │
274
- │ │ scheduleId: `wf-${workflowId}-cron`, │ │
275
- │ │ spec:{ cronExpressions:['0 2 * * *'] }, │ │
276
- │ │ action:{ type:'startWorkflow', │ │
277
- │ │ workflowType:'cronTriggerWorkflow', │ │
278
- │ │ args:[{ workflowId, versionId, dsl }] │ │
279
- │ │ } │ │
280
- │ │ }) │ │
281
- │ ├──────────────────────────────────────────►│ │
282
- │ │ │ │ Server 写 temporal.schedules + │
283
- │ │ │ │ 内部 timer 任务 │
284
- │ │◄──── ok ──────────────────────────────────┤ │
285
- │◄── 200 OK ──────┤ │ │ │
286
- │ │ │ │ │
287
- ⋮ ⋮ ⋮ (等到 cron 时间到,Server 自动起 Workflow) ⋮
288
- │ │ │ │ │
289
- │ │ │ │ Server: 写 executions / │
290
- │ │ │ │ history_node + 投 WorkflowTask│
291
- │ │ │ │ │
292
- │ │ │ │ PollWorkflowTask│
293
- │ │ │ │◄────────────────────────────┤
294
- │ │ │ ├──── task ──────────────────►│
295
- │ │ │ │ │ 1) cronTriggerWorkflow:
296
- │ │ │ │ │ Activity:createInstance
297
- │ │ │ INSERT t_workflow_instance (creator='cron') │
298
- │ │ │◄──────────────────────────────────────────────── ┤
299
- │ │ │ │ │ 2) executeChildWorkflow
300
- │ │ │ │ │ ('dslWorkflow', { instanceId, dsl, … })
301
- │ │ │ │ │ ─→ 与场景 A 第 5 步起完全一致
302
- │ │ │ INSERT/UPDATE t_workflow_node_execution │
303
- │ │ │◄──────────────────────────────────────────────── ┤
304
- │ │ │ UPDATE t_workflow_instance (status, ended_at) │
305
- │ │ │◄──────────────────────────────────────────────── ┤
306
- ```
307
-
308
- **编辑/取消调度时:**
309
-
310
- | 用户操作 | NestJS 调用 | 说明 |
311
- |----------|-------------|------|
312
- | 修改 cron 表达式(发布新版本) | `client.schedule.getHandle(id).update(...)` | 原 schedule 改 spec,不会丢历史 |
313
- | 暂停 / 启用 Workflow | `handle.pause()` / `handle.unpause()` | Server 跳过触发但保留 schedule |
314
- | 删除 Workflow / 取消发布 | `handle.delete()` | 同时清掉 schedule,避免遗留 |
315
-
316
- **涉及的 PG 表:**
317
-
318
- | 表 | 操作 | 写入方 |
319
- |----|------|--------|
320
- | `t_workflow` | UPDATE status | NestJS(发布动作) |
321
- | `t_workflow_instance` | INSERT (creator='cron') → UPDATE status | Worker `cronTriggerWorkflow` 内的 createInstance Activity |
322
- | `t_workflow_node_execution` | INSERT/UPDATE | Worker `reporting.ts` |
323
- | `temporal.schedules` / `schedules_by_namespace` | INSERT/UPDATE/DELETE schedule 记录 | Temporal Server(Frontend Service) |
324
- | `temporal.executions` / `history_node` / `task_queues` | 同场景 A | Temporal Server(Schedule 触发后) |
325
-
326
- **为什么不用 outbox-poller 模式 / AgendaService?**
327
-
328
- | 方案 | 缺点 |
329
- |------|------|
330
- | Worker 内 cron-poller(仿 outbox-poller) | 多 Worker 实例需自己加分布式锁,且会跟 Temporal 状态机割裂 |
331
- | 复用 AgendaService | Agenda 跑在 NestJS 进程,Workflow 调度跨两个时序源,故障定位复杂 |
332
- | **Temporal Schedule API** ✅ | Server 原生支持,自动持久化到 `temporal.schedules`,UI 可见、支持 pause/backfill/skip |
333
-
334
- ---
335
-
336
- ### 场景 D:NestJS Public 端点(外部 webhook 触发,`webhook_trigger`)
337
-
338
- > ✅ **当前状态**:已实现(HMAC 严格模式)。`POST /api/workflow/webhook/:path` 由 `WorkflowController.receiveWebhook` 接收,路由用 `@AllowUnauthorizedRequest()` 跳过登录态。请求必须带 `X-Workflow-Timestamp`(±5min)+ `X-Workflow-Signature: sha256=HEX(HMAC_SHA256(secret, "{ts}.{rawBody}"))`。验签失败 / 路径未匹配 / 时间戳过期分别返回 401 / 404 / 401。
339
-
340
- 入口:外部系统(如 GitHub / Stripe / 内部其他服务)按 Workflow 编辑器配置的 `webhookPath` 发 HTTP 请求 → NestJS 公开路由(无需登录态)匹配 published workflow 并复用 `executeWorkflow()` 流程。
341
-
342
- ```
343
- ┌──────────┐ ┌────────────────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌────────────────┐ ┌────────────┐
344
- │ 外部系统 │ │ NestJS │ │ NestJS │ │ PostgreSQL │ │ Temporal Server│ │ Worker │
345
- │ (任何 HTTP│ │ WebhookController │ │ WorkflowService │ │ (业务 schema) │ │ (gRPC :7233) │ │ (long-poll)│
346
- │ 客户端) │ │ @Public │ │ │ │ │ │ │ │ │
347
- └────┬─────┘ └──────────┬─────────┘ └────────┬─────────┘ └────────┬────────┘ └───────┬────────┘ └─────┬──────┘
348
- │ POST /api/workflow/webhook/:path │ │ │ │
349
- │ Headers: X-Signature, body=payload │ │ │ │
350
- ├──────────────────►│ │ │ │ │
351
- │ │ (可选) 校验签名 / IP 白名单 │ │ │
352
- │ │ │ │ │ │
353
- │ │ SELECT workflow JOIN version 找含 webhook_trigger 节点 │ │
354
- │ │ 且 config.webhookPath = :path 的最新发布版本 │ │
355
- │ ├──────────────────────────────────────────────►│ │ │
356
- │ │◄──────── 0 / 1 / N 个匹配 workflow ───────────┤ │ │
357
- │ │ │ │ │ │
358
- │ │ 对每个匹配的 workflow(通常仅 1 个): │ │ │
359
- │ │ executeWorkflow(workflowId, { context: { headers, query, body } }, creator='webhook') │
360
- │ ├──────────────────────►│ │ │ │
361
- │ │ │ INSERT t_workflow_instance (creator='webhook', context=请求体) │
362
- │ │ ├─────────────────────►│ │ │
363
- │ │ │◄────── instanceId ───┤ │ │
364
- │ │ │ │ │ │
365
- │ │ │ temporalService.startWorkflow('dslWorkflow', …) │
366
- │ │ ├──────────────────────────────────────────►│ │
367
- │ │ │ │ 写 executions / history_node │
368
- │ │ │ │ + 投 WorkflowTask │ │
369
- │ │ │◄────── runId ────────────────────────────┤ │
370
- │ │ │ UPDATE t_workflow_instance SET temporal_run_id│ │
371
- │ │ ├─────────────────────►│ │ │
372
- │ │◄── { instanceId } ────┤ │ │ PollWorkflowTask │
373
- │ │ │ │ │◄──────────────────┤
374
- │◄── 202 Accepted { instanceIds } ──────────┤ │ ├── task ──────────►│
375
- │ (异步:webhook 立刻返回,工作流后台跑) │ │ │ │ 执行 dslWorkflow
376
- │ │ │ │ INSERT/UPDATE t_workflow_node_execution│
377
- │ │ │ │◄──────────────────────────────────────┤
378
- │ │ │ │ UPDATE t_workflow_instance (status) │
379
- │ │ │ │◄──────────────────────────────────────┤
380
- ```
381
-
382
- **关键设计点:**
383
-
384
- | 关注点 | 推荐做法 |
385
- |--------|----------|
386
- | 鉴权 | NestJS Controller 用 `@Public()` 跳过登录态,但每个 workflow 自带 `webhookSecret`,请求需带 `X-Signature: HMAC_SHA256(secret, body)` |
387
- | 路径冲突 | `webhookPath` 必须在 published workflow 间唯一(Publish 时校验冲突,或用 `wf_${workflowId}` 做后缀) |
388
- | 同步 vs 异步 | 默认异步:返回 `202 Accepted { instanceId }`,由前端轮询 `/instance/:id` 看结果。同步模式可选:等到 workflow 完成再返回(注意网关超时) |
389
- | 多 workflow 订阅同一路径 | 与 outbox 一致:并行启动多个 instance,各自独立 |
390
- | 重放保护 | 校验 `X-Timestamp` 在 ±5min 内 + payload 哈希做去重(可选,存 Redis 5min TTL) |
391
-
392
- **涉及的 PG 表:**
393
-
394
- | 表 | 操作 | 写入方 |
395
- |----|------|--------|
396
- | `t_workflow`、`t_workflow_version` | SELECT 含 `webhook_trigger` 节点的最新发布版本 | NestJS `WebhookController` |
397
- | `t_workflow_instance` | INSERT (creator='webhook') → UPDATE temporal_run_id → UPDATE status | NestJS 创建;Worker `reporting.ts` 改状态 |
398
- | `t_workflow_node_execution` | INSERT/UPDATE | Worker `reporting.ts` |
399
- | `temporal.executions` / `history_node` / `task_queues` | 同场景 A | Temporal Server |
400
-
401
- **为什么不让 Worker 自己开 HTTP 端口接收 webhook?**
402
-
403
- - Worker 是无状态计算节点,不应面向公网;外部网络/网关/WAF 都暴露在 NestJS 那一层
404
- - NestJS 已有完善的 Guard、Logger、Swagger、错误处理体系,复用更合适
405
- - 校验签名、限流、IP 白名单等都属于"边界关注点",归属 Web 层
406
-
407
- ---
408
-
409
- ### 一句话对比(四种触发场景)
410
-
411
- | | 场景 A:NestJS startWorkflow | 场景 B:Outbox Poller | 场景 C:Temporal Schedule | 场景 D:NestJS Webhook |
412
- |--|------------------------------|------------------------|----------------------------|--------------------------|
413
- | **当前状态** | ✅ 已实现 | ✅ 已实现 | ✅ 已实现 | ✅ 已实现(HMAC 严格) |
414
- | 触发点 | 用户/定时业务调用 NestJS | 业务表 DML → 触发器 → outbox | Server 内置 cron 调度 | 外部 HTTP POST |
415
- | 入口节点 | (任何,无 trigger 节点) | `event_trigger` | `cron_trigger` | `webhook_trigger` |
416
- | 谁创建 `t_workflow_instance` | NestJS `WorkflowService` | Worker `outbox-poller` | Worker(cronTriggerWorkflow 内 Activity) | NestJS `WorkflowService` |
417
- | 谁调用 `workflow.start()` | NestJS `TemporalService` | Worker `outbox-poller` | **Temporal Server 自己**(Schedule 系统) | NestJS `TemporalService` |
418
- | 是否走 Temporal Server | ✅ | ✅ | ✅(且 schedule 本身也由 Server 持久化) | ✅ |
419
- | `creator` 字段 | 登录用户 | `'event_trigger'` | `'cron'` | `'webhook'` |
420
- | 触发延迟 | <100ms | ≤ 轮询间隔(默认 10s) | 由 Server 内部 timer 控制(秒级精度) | <100ms |
421
- | 失败重试 | Workflow/Activity 自带重试 | outbox 行重试 ≤ `OUTBOX_MAX_RETRIES` | Schedule 不重试触发;Workflow 内部正常重试 | NestJS 直接返回 4xx/5xx;webhook 客户端自己决定是否重投 |
422
- | 多实例并发安全 | NestJS 多 Pod 各自 OK | `FOR UPDATE SKIP LOCKED` 防重复消费 | Server 单一 schedule 实例,天然不重复 | NestJS 多 Pod 各自 OK |
423
-
424
- **关键点:** 不论从哪个入口触发,只要调用了 `temporalClient.workflow.start()`(或 Schedule 间接调用),就一定要经过 Temporal Server 的调度(写 history → 入 task queue → Worker long-poll 拉取),Server 是**唯一的状态权威**。Worker 内部直接读写业务表只发生在 Activity 执行阶段(`reporting.ts` 写 `t_workflow_node_execution` / `t_workflow_instance`,outbox-poller 写 outbox 与 instance),这是绕不过 Server 的——但写 Workflow 历史和调度本身仍由 Server 完成。
425
-
426
- ## 环境变量
427
-
428
- | 变量 | 默认值 | 说明 |
429
- |------|--------|------|
430
- | `TEMPORAL_ADDRESS` | `localhost:7233` | Temporal Server gRPC 地址 |
431
- | `TEMPORAL_NAMESPACE` | `default` | Temporal 命名空间 |
432
- | `TEMPORAL_TASK_QUEUE` | `workflow-execution` | 任务队列名称 |
433
- | `DATABASE_URL` | (见 .env) | Worker 回写状态用的数据库连接 |
434
-
435
- ## 常用命令
436
-
437
- ```bash
438
- npm run up # 启动 Server + UI
439
- npm run down # 停止 Server + UI
440
- npm run logs # 查看 Server 日志
441
- npm run worker:dev # 开发模式启动 Worker
442
- npm run worker:build # 编译 Worker
443
- npm run worker:start # 生产模式启动 Worker
444
- ```
445
-
446
- ## Web UI
447
-
448
- 启动后访问 http://localhost:8080 可查看:
449
- - 运行中的 Workflow 列表
450
- - Workflow 执行历史
451
- - 单个 Workflow 的事件时间线
452
- - Worker 连接状态
453
-
454
- ---
455
-
456
- ## Event Trigger(数据库事件触发 Workflow)
457
-
458
- 通过 Transactional Outbox 模式,当业务表发生变更时自动触发对应的 Workflow。
459
-
460
- ### 原理
461
-
462
- ```
463
- ┌─────────────┐ INSERT/UPDATE/DELETE ┌──────────────────────────┐
464
- │ 任何来源 │ ────────────────────────→ │ PostgreSQL │
465
- │ (NestJS / │ │ │
466
- │ 外部系统) │ │ AFTER TRIGGER │
467
- └─────────────┘ │ → INSERT INTO outbox │
468
- │ (同一事务,保证不丢) │
469
- └─────────────┬─────────────┘
470
- │
471
- outbox 表中有新行
472
- │
473
- ▼
474
- ┌──────────────────────────┐
475
- │ Worker │
476
- │ outbox-poller.ts │
477
- │ │
478
- │ 每 10s 扫描一次: │
479
- │ 1. SELECT 未处理行 │
480
- │ 2. 匹配 event_trigger │
481
- │ 3. 启动 Temporal workflow │
482
- │ 4. 标记行为已处理 │
483
- └──────────────────────────┘
484
- ```
485
-
486
- ### 为什么选轮询而不是 PG LISTEN/NOTIFY
487
-
488
- | | PG NOTIFY | Outbox 轮询 |
489
- |--|--|--|
490
- | Worker 离线期间 | ❌ 事件丢失 | ✅ 事件留在表中,恢复后处理 |
491
- | 可靠性 | best-effort | **at-least-once(事务保证)** |
492
- | 复杂度 | 需要长连接 + 去重 | 只需定时 SELECT |
493
- | 延迟 | <100ms | ≤ 轮询间隔(默认 10s) |
494
-
495
- ### 支持的操作类型
496
-
497
- | 数据库操作 | eventName 示例 |
498
- |-----------|---------------|
499
- | 新增记录 | `t_order.insert` |
500
- | 修改记录 | `t_order.update` |
501
- | 删除记录 | `t_order.delete` |
502
-
503
- ### eventName 通配符匹配
504
-
505
- 在 Workflow DSL 的 `event_trigger` 节点中配置 `eventName`:
506
-
507
- | eventName 配置 | 触发条件 |
508
- |---|---|
509
- | `t_order.insert` | 仅该表新增时触发 |
510
- | `t_order.update` | 仅该表修改时触发 |
511
- | `t_order.delete` | 仅该表删除时触发 |
512
- | `t_order.*` | 该表任何变更都触发 |
513
- | `*.insert` | 所有表的新增都触发 |
514
-
515
- ### 使用步骤
516
-
517
- **Step 1:创建 outbox 表 + 通用触发器函数(只需执行一次)**
518
-
519
- Outbox 表 `t_workflow_event_outbox` 由 Prisma 模型 `WorkflowEventOutbox` 维护,跟随 `prisma migrate` 流程自动建表;通用触发器函数 `notify_workflow_event()` 和未处理事件的部分索引 `idx_outbox_unprocessed` 由 seed 脚本创建:
520
-
521
- ```bash
522
- # server 目录
523
- cd server
524
- yarn seed
525
- ```
526
-
527
- 幂等可重复执行。详见 `server/seed/workflow-event-trigger.ts`。
528
-
529
- **Step 2:给需要监听的表挂触发器**
530
-
531
- ```sql
532
- -- 只监听 INSERT
533
- CREATE TRIGGER trg_order_workflow
534
- AFTER INSERT ON t_order
535
- FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
536
-
537
- -- 监听所有变更(INSERT + UPDATE + DELETE)
538
- CREATE TRIGGER trg_order_workflow
539
- AFTER INSERT OR UPDATE OR DELETE ON t_order
540
- FOR EACH ROW EXECUTE FUNCTION notify_workflow_event();
541
-
542
- -- 只在特定条件下触发(例如状态变为 APPROVED)
543
- CREATE OR REPLACE FUNCTION notify_order_approved() RETURNS trigger AS $$
544
- BEGIN
545
- IF NEW.status = 'APPROVED' AND (OLD.status IS NULL OR OLD.status != 'APPROVED') THEN
546
- INSERT INTO t_workflow_event_outbox (event_name, payload)
547
- VALUES ('t_order.status_approved', row_to_json(NEW)::jsonb);
548
- END IF;
549
- RETURN NEW;
550
- END;
551
- $$ LANGUAGE plpgsql;
552
-
553
- CREATE TRIGGER trg_order_approved
554
- AFTER UPDATE ON t_order
555
- FOR EACH ROW EXECUTE FUNCTION notify_order_approved();
556
- ```
557
-
558
- **Step 3:在 Workflow 编辑器中配置 event_trigger 节点**
14
+ ## 快速上手(Phase 3 后新开发者视角)
559
15
 
560
- ```json
561
- {
562
- "type": "event_trigger",
563
- "config": {
564
- "eventName": "t_order.insert",
565
- "filterExpression": "data.amount > 100"
566
- }
567
- }
568
- ```
16
+ 1. 确认共享 temporal 已在本机或内网跑起来(本机方案见
17
+ `common/gadmin2/temporal-shared/README.md`)
18
+ 2. `cp server/.env server/.env.local` 并填入 6 个变量(下方清单)
19
+ 3. 首次注册本项目为共享 temporal 的租户:`bash dev/register-tenant.sh`
20
+ 4. `docker compose up -d` — 4 个 service 起来(postgres / server-init / server / web)
21
+ 5. Web UI 触发 workflow → 到共享 temporal UI(默认 http://localhost:8080)查看
569
22
 
570
- - `eventName`:匹配规则(支持通配符)
571
- - `filterExpression`(可选):JS 表达式,对 payload 的过滤条件
23
+ ## `.env.local` 需要填的 6 个变量
572
24
 
573
- **Step 4:发布 Workflow**
25
+ | 变量 | 用途 | 示例 |
26
+ |------|------|------|
27
+ | `TEMPORAL_ADDRESS` | 共享 temporal 的 gRPC 地址 | `localhost:7233` |
28
+ | `TEMPORAL_NAMESPACE` | 本项目在共享 temporal 里的 namespace | `gadmin-test` |
29
+ | `SHARED_TEMPORAL_ADMIN_URL` | 共享 temporal admin API | `http://localhost:8081` |
30
+ | `SHARED_TEMPORAL_ADMIN_TOKEN` | admin API token | 问运维;32 hex |
31
+ | `GADMIN_TENANT_NAMESPACE` | admin API 里 tenant 的 namespace(= `TEMPORAL_NAMESPACE`) | `gadmin-test` |
32
+ | `SHARED_TENANT_DB_URL` | 共享 worker 视角看到的本项目 db url | `postgresql://kavenma:kavenma@host.containers.internal:5432/gadmin_demo` |
574
33
 
575
- 发布后 Worker 的 outbox-poller 会自动匹配新事件到该 Workflow。
34
+ **为什么 `SHARED_TENANT_DB_URL` 与 `server/.env` 里的 `DATABASE_URL` 不同**:本项目内部
35
+ 服务(server / server-init)走 docker network 用 `postgresql://.../postgres:5432/gadmin_demo`;
36
+ 共享 worker 在独立 podman/docker 网络里,必须走 host 网络才能到达本项目的 PG,因此
37
+ 用 `host.containers.internal:5432` 或 `host.docker.internal:5432`。见设计文档 §2.2。
576
38
 
577
- ### 配置
39
+ ## 健康检查
578
40
 
579
- | 环境变量 | 默认值 | 说明 |
580
- |---------|--------|------|
581
- | `EVENT_POLL_INTERVAL_MS` | `10000` | 轮询间隔(毫秒) |
41
+ - 共享 temporal server 健康:`curl -s $TEMPORAL_ADDRESS/health` (7233 是 gRPC,不能直接
42
+ curl;用 `docker compose ps` 看 `shared-temporal-server` 状态 healthy 更方便)
43
+ - 共享 admin API:`curl -s $SHARED_TEMPORAL_ADMIN_URL/health/live` → `{"status":"ok"}`
44
+ - 本项目租户已注册:`curl -sH "X-Admin-Token: $SHARED_TEMPORAL_ADMIN_TOKEN" \
45
+ $SHARED_TEMPORAL_ADMIN_URL/admin/tenants | grep gadmin-test`
582
46
 
583
- ### 注意事项
47
+ ## 回滚
584
48
 
585
- - Outbox 表会持续增长,建议定期清理已处理的旧行(如保留 7 天)
586
- - 同一个 eventName 可以匹配多个 Workflow,它们会并行启动
587
- - `filterExpression` 在 Worker 侧求值,不满足条件的事件仍会写入 outbox 但不触发 workflow
49
+ `git checkout phase-2-complete` 一条命令回到 Phase 2 收尾(temporal/ 目录 + 3 个
50
+ docker-compose service 全部复活)。之后 `docker compose up -d` 走老流程。无需 DB
51
+ 迁移,新老 worker 都写同一张 `t_workflow_node_execution` 表结构。