This commit is contained in:
oneao committed 2026-10-13 22:32:50 +08:00
1 parent f3936cec61
commit 15e0ae5b67
115 files changed
+11399 -2741

No files matched your search

@@ -0,0 +1,393 @@
---
title: 流程配置
description: 流程定义、版本草稿、发布校验与模块绑定
pageClass: g3-api
---
# 流程配置
配置侧接口,前缀 `/workflow/**`。
**读取走通用接口**(列表用 `v_wf_definition` / `v_wf_version` / `v_wf_binding` 视图,设计器读回草稿用 `/data/loaddata` 查 `wf_*` 表);**写入一律走这里**——发布校验、版本快照、「已发布不可改」这些约束必须在后端一个事务内完成,通用保存接口不参与。
> 运行侧(送审、审批动作、待办、抄送)见[流程运行](/api/workflow-runtime)。
## 流程定义
### `POST` `/workflow/definition/save`
新建或修改流程定义。**新建时同时落一条空草稿的 v1 版本**,保证设计器一进去就有可保存的目标。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `definitionId` | string | 是 | 流程编码。新建时校验:**字母开头**,仅字母/数字/下划线,≤ 50 字符 |
| `name` | string | 是 | 流程名称 |
| `i18n` | string | | 多语言资源键 |
| `bz` | string | | 备注 |
| `canuse` | bool | | 是否启用,默认 `true` |
</G3ApiParams>
<G3ApiPayload title="请求体">
```json
{
"definitionId": "expense_approval",
"name": "费用报销审批",
"i18n": "workflow.expense_approval",
"canuse": true
}
```
</G3ApiPayload>
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "保存成功",
"data": {
"definitionId": "expense_approval",
"versionId": "expense_approval_v1",
"created": true
}
}
```
</G3ApiPayload>
- `created` 为 `true` 表示**新建**(同时建了 v1 草稿);`false` 表示改的是已存在的定义。
- 修改已存在的定义时,`versionId` 返回该定义**当前最新的草稿版本**(没有草稿则为 `null`)。
### `POST` `/workflow/definition/delete`
删除流程定义,连同它的版本、节点、连线、审批人规则与绑定**一起删**。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `definitionId` | string | 是 | 流程编码 |
</G3ApiParams>
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "删除成功",
"data": { "versions": 2, "bindings": 1 }
}
```
</G3ApiPayload>
> **有未结束实例时会拒绝删除**:`1000`「该流程还有 N 个未结束的实例,不能删除」。
> 运行历史必须能追溯到版本,不能留下悬空引用。未结束指状态为 `running` / `returned` / `error`。
## 版本与草稿
### `POST` `/workflow/version/draft/save`
保存草稿:**全量替换**该版本的节点 / 连线 / 审批人规则(幂等)。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `definitionId` | string | 是 | 流程编码 |
| `versionId` | string | | 目标版本;**留空则取该定义的草稿,没有就新建** |
| `draft` | object | 是 | 草稿内容 `{ nodes, edges }`,见下 |
| `canvas` | object | | 画布布局(只存不参与执行) |
</G3ApiParams>
`draft` 的结构与前端设计器的领域模型一致:
<G3ApiPayload title="draft 结构">
```json
{
"nodes": [
{
"nodeKey": "start",
"nodeType": "start",
"name": "开始",
"config": {},
"actorRules": []
},
{
"nodeKey": "dept_manager",
"nodeType": "user_task",
"name": "部门负责人审批",
"config": {
"approvalMode": "any",
"rejectMode": "return_initiator",
"dueHours": 24,
"allowClaim": true,
"allowInitiator": false,
"excludePreviousActors": true
},
"actorRules": [
{ "actorType": "dept_manager", "fallbackType": "admin" }
]
}
],
"edges": [
{ "edgeKey": "e1", "from": "start", "to": "dept_manager" }
]
}
```
</G3ApiPayload>
**字段说明**:
| 位置 | 字段 | 说明 |
|---|---|---|
| node | `nodeKey` | 版本内稳定编码,≤ 50 字符,**不可跨版本改**(运行时连线状态按它映射) |
| node | `nodeType` | `start` / `end` / `user_task` / `cc_task` / `service_task` / `exclusive_gateway` / `parallel_gateway` |
| node | `config.approvalMode` | `single` / `any` / `all` / `serial` / `n_of_m` / `percentage`。**留空按 `single` 处理**;无法识别的取值也按 1 票通过 |
| node | `config.rejectMode` | `terminate` / `return_initiator` / `return_previous` |
| node | `config.dueHours` | 审批时限(小时);**不配则任务没有应处理时间,永不超时** |
| node | `config.requiredCount` / `requiredRatio` | 配合 `n_of_m` / `percentage` 用 |
| node | `config.allowInitiator` | **显式 `false`** 才排除发起人(缺省允许) |
| node | `config.excludeCreator` | 排除创建人(当前以发起人近似) |
| node | `config.excludePreviousActors` | 排除此前节点出现过的人 |
| node | `config.allowClaim` | 是否允许抢占待办 |
| edge | `condition` | 条件 AST;**留空 = 无条件(默认边)** |
| rule | `actorType` | `user` / `initiator` / `field_user` / `dept_manager` / `initiator_manager` / `position` / `org_role` |
| rule | `fallbackType` | 解析为空时的兜底:`admin` / `initiator` |
> **`condition` 必须是对象或留空**。给了值却不是对象会直接报错——把脏条件静默当成「无条件边」会让流程走错分支,所以发布期就拒绝。
> **`actorRules` 的数组顺序就是串行会签的先后顺序**。
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "草稿已保存",
"data": {
"versionId": "expense_approval_v1",
"versionNo": 1,
"issues": [],
"edgeKeyMap": {}
}
}
```
</G3ApiPayload>
- `issues` 是**保存时顺带做的校验提示**(不阻断保存)。
- `edgeKeyMap` 只在连线编码因与其它版本冲突而被换新 ID 时出现,前端据此把本地 `edgeKey` 对齐到库里。
> 已发布版本**一律拒绝**保存草稿(`1000`)——改流程要复制新版本。
### `POST` `/workflow/version/validate`
校验草稿。传 `draft` 校验内存草稿(设计器边改边校验),传 `versionId` 读库校验。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `draft` | object | 二选一 | 内存草稿 |
| `versionId` | string | 二选一 | 读库校验的版本 |
</G3ApiParams>
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "操作成功",
"data": {
"issues": [
{ "level": "error", "message": "节点「部门负责人审批」没有配置审批人规则" },
{ "level": "warn", "message": "节点「财务复核」没有配置审批时限" }
],
"errorCount": 1,
"warnCount": 1
}
}
```
</G3ApiPayload>
### `POST` `/workflow/version/publish`
发布版本:校验必须**零 error**,然后计算配置摘要并锁定节点与连线。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `versionId` | string | 是 | 要发布的草稿版本 |
</G3ApiParams>
<G3ApiPayload title="发布成功" :code="0">
```json
{
"code": 0,
"message": "发布成功",
"data": {
"published": true,
"checksum": "3f2a...",
"definitionId": "expense_approval",
"issues": []
}
}
```
</G3ApiPayload>
<G3ApiPayload title="校验未通过(仍是 code 0)" :code="0">
```json
{
"code": 0,
"message": "发布校验未通过",
"data": {
"published": false,
"issues": [
{ "level": "error", "message": "流程缺少结束节点" }
]
}
}
```
</G3ApiPayload>
> **校验不通过不是错误**:HTTP 与 `code` 都是成功,要靠 `data.published` 判断。
> 这样调用方能**一次看到全部问题**,而不是改一个报一个。
### `POST` `/workflow/version/retire`
停用版本:`published` → `retired`,不再接受新实例;**已有实例继续用原版本跑完**。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `versionId` | string | 是 | 要停用的已发布版本 |
</G3ApiParams>
### `POST` `/workflow/version/copy`
以某个版本为模板复制出一条**新草稿**(版本号 = 当前最大值 + 1)。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `versionId` | string | 是 | 源版本(可以是已发布版本) |
</G3ApiParams>
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "已复制为新草稿",
"data": { "versionId": "expense_approval_v3", "versionNo": 3 }
}
```
</G3ApiPayload>
> **同一定义同时只允许一条草稿**。已有草稿时会报 `1000`「该流程已有草稿版本「x」,请先发布或删除它」——避免「改的是哪一条」说不清。
## 流程绑定
绑定决定「哪个模块的哪个业务事件走哪个流程版本」。
### `POST` `/workflow/binding/save`
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `moduleId` | string | 是 | 模块编码 |
| `event` | string | 是 | 业务事件:`submit` / `resubmit` / `change` |
| `versionId` | string | 是 | 目标版本,**必须是已发布版本** |
| `bindingId` | string | | 留空=新建,传值=修改 |
| `match` | object | | 匹配条件(条件 AST),决定业务上下文是否走这个流程 |
| `orgScope` | object | | 组织范围 |
| `statusField` | string | | 业务表上的**状态列名**(状态投影写这一列) |
| `statusMap` | object | | 流程状态 → 业务状态值的映射 |
| `priority` | int | | 优先级,默认 0,**小的先匹配** |
| `effectiveFrom` / `effectiveTo` | string | | 生效区间 |
| `canuse` | bool | | 是否启用,默认 `true` |
| `bz` | string | | 备注 |
</G3ApiParams>
<G3ApiPayload title="请求体">
```json
{
"moduleId": "v_b_othercompany",
"event": "submit",
"versionId": "expense_approval_v1",
"match": { "field": "b_amount", "operator": "gt", "value": 0 },
"statusField": "b_status",
"statusMap": { "approved": 2, "rejected": 3, "returned": 4 },
"priority": 10,
"canuse": true
}
```
</G3ApiPayload>
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "保存成功",
"data": { "bindingId": "1234567890123", "created": true }
}
```
</G3ApiPayload>
**几条硬规则**:
- **绑定只能指向已发布版本**(`1000`,提示当前版本状态)。
- **同一模块 + 同一事件下,不能有两条同优先级的启用绑定**(`1000`)——否则无法确定用哪个流程。冲突时调整 `priority` 或停用其中一条。
- **保存绑定会顺带备好该模块的动作权限点**:否则管理员在用户权限页看不到可授权的权限点,非管理员送审会被白名单拒绝。这一步是幂等的,不会覆盖管理员改过的名称。
> `match` 留空表示**该模块该事件一律走这个流程**。`statusMap` 决定审批结果如何写回业务表状态列;不配 `statusField` 则跳过状态投影(只读模块的正常情况)。
### `POST` `/workflow/binding/delete`
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `bindingId` | string | 是 | 绑定 ID |
</G3ApiParams>
## 常见错误
| 场景 | 表现 |
|---|---|
| 流程编码非法 | `1000`「流程编码只能以字母开头,由字母、数字、下划线组成,且不超过 50 个字符」 |
| 版本不存在 | `1000`「版本「x」不存在」 |
| 非草稿版本发布 | `1000`「只有草稿版本可以发布,当前版本状态:published」 |
| 非已发布版本停用 | `1000`「只有已发布版本可以停用,当前版本状态:draft」 |
| 绑定指向非已发布版本 | `1000`「绑定只能指向已发布版本,当前版本状态:draft」 |
| 同优先级启用绑定冲突 | `1000`「同一模块同一事件下已存在同优先级的启用绑定「x」」 |
| 删除有未结束实例的定义 | `1000`「该流程还有 N 个未结束的实例,不能删除」 |
| 连线/审批人规则条件不是对象 | `1000`,指明是「审批人规则」还是「连线「x」」的条件 |