跳到主要内容

流程设计

节点类型

节点类型常量Wire value说明
开始NodeStartstart工作流入口
审批NodeApprovalapproval需要审批人执行审批动作
办理NodeHandlehandle需要处理人执行办理动作
条件NodeConditioncondition基于条件进行分支
抄送NodeCCcc向指定用户发送通知
结束NodeEndend工作流终点

条件分支

条件节点会按 priority 顺序评估 ConditionBranch。每个分支包含一个或多个 ConditionGroup:同一个 group 内的条件按 AND 组合,同一分支上的多个 group 按 OR 组合。

ConditionField 使用结构化的 Subject / Operator / Value 字段。 Operator 类型是 ConditionOperator;公开常量包括 OperatorEqualsOperatorNotEqualsOperatorGreaterOperatorGreaterOrEqOperatorLessOperatorLessOrEqOperatorInOperatorNotInOperatorContainsOperatorNotContainsOperatorStartsWithOperatorEndsWithOperatorIsEmptyOperatorIsNotEmpty。内置 evaluator 会把 field condition 转换成 expr-lang 表达式;未知 operator 会被转换成恒为 false 的表达式。

ConditionExpression 直接用 expr-lang 执行原始 Expression 字符串。 评估环境暴露:

名称
formData当前实例的 FormData map
applicantId当前申请人 ID
applicantDepartmentId申请人部门 ID;不存在时是 ""
globals宿主解析出的 Instance.Globals,作为顶层 binding 暴露

审批条件目前有意直接使用 expr-lang,不走公开的 expression.Engine wrapper。这样工作流条件语义由审批 evaluator 固定,不依赖 expression module 的装配方式。

宿主应用可以实现 approval.InstanceGlobalsResolver,在实例启动时根据已认证 principal 解析全局变量。该快照会持久化到 Instance.Globals;客户端不能在 start 请求体里提交它。Field condition 会先从 globals 解析 Subject,再查 formData;expression condition 会把 globals 暴露为顶层 binding,但内置的 formDataapplicantIdapplicantDepartmentId 名称发生冲突时优先级更高。

明细表格聚合

字段条件可以不比较标量 subject,而是对明细表格的行做聚合(v0.36)。条件仍然 是结构化的——没有字符串 DSL:subject 指定表格字段,aggregate 选择聚合 方式,column 指定要聚合的数字列。

AggregateKindWire valuecolumn聚合结果
AggregateSumsum必填指定数字列的求和
AggregateCountcount禁止填写行数
AggregateAvgavg必填指定数字列的平均值

AggregateKind.FoldsColumn() 报告某个 kind 聚合的是列(sum / avg) 还是行(count)。聚合本身通过 approval.Aggregator 接口可插拔:

type Aggregator interface {
// Kind 返回该实现所聚合的 aggregate kind。
Kind() AggregateKind
// Fold 把提取出的列值(或行数)归约成比较操作数。
// matchable=false 表示该聚合对输入没有定义值——例如对零行求 avg——
// 此时条件必须不匹配,与 SQL NULL 比较语义一致。
Fold(values []float64, rowCount int) (result float64, matchable bool)
}

vef.ProvideApprovalAggregator 在内置 sum / count / avg 之外注册 自定义聚合器;条件 evaluator 按其 AggregateKind 自动拾取,无需改动已有代码:

vef.Run(
vef.ApprovalModule,
vef.ProvideApprovalAggregator(func() approval.Aggregator { return myMedian{} }),
app.Module,
)

审批方式

当节点有多个审批人时:

方式常量Wire value行为
顺序ApprovalSequentialsequential审批人按顺序逐个处理
并行ApprovalParallelparallel审批人同时处理

枚举类型是 ApprovalMethod

通过规则(并行模式)

规则常量Wire value行为
全部PassAllall所有审批人必须同意
任意PassAnyany至少一人同意即通过
比例PassRatioratio达到一定比例即通过

自定义通过规则实现使用 PassRuleStrategyPassRuleContext,并返回 PassRuleResultPassRulePendingPassRulePassedPassRuleRejected)。

审批人类型

类型常量Wire value说明
指定用户AssigneeUseruser特定用户
角色AssigneeRolerole拥有某角色的用户
部门AssigneeDepartmentdepartment部门负责人
申请人本人AssigneeSelfself申请人自己
直接上级AssigneeSuperiorsuperior直接上级
部门领导链AssigneeDepartmentLeaderdepartment_leader多级主管链
表单字段AssigneeFormFieldform_field由表单字段值决定

枚举类型是 AssigneeKind。动态加签位置使用 AddAssigneeTypeAddAssigneeBeforebefore)、AddAssigneeAfterafter)、 AddAssigneeParallelparallel)。

节点字段权限

任务节点(TaskNodeData,被审批和办理节点嵌入)和抄送节点(CCNodeData) 携带 fieldPermissions map:表单字段 key → Permission。取值如下:

常量Wire value对该节点参与者的含义
PermissionVisiblevisible只读
PermissionEditableeditable可以提交新值
PermissionHiddenhidden不展示
PermissionRequiredrequired可编辑且必须提供

缺失的 key 视为 visible。部署校验(v0.38)会对照派生的表单字段检查这个 map:每个 key 必须引用一个顶层表单字段,取值必须在枚举内,抄送节点只能使用 visible / hidden 子集,并且当节点的超时动作解析为 auto_pass 时会拒绝 required 权限(超时扫描器的 auto-pass 结单时不会执行必填检查)。

该 map 在写路径上强制生效:任务处理时,提交的 formData 只会合并 fieldPermissions 中标为 editablerequired 的字段,且合并的子集会 再按字段定义重新校验。required 的必填检查只在 approvehandle 决策时执行——rejecttransferrollback 保持豁免。没有配置 fieldPermissions 的节点不授予任何写权限:提交的表单数据会被丢弃。

在读侧,my.get_instance_detail 返回按查看者投影的 fieldPermissions: 框架在查看者的参与上下文(本人任务、抄送、申请人)上对格 (hidden < visible < editable < required)做 max-merge,只有 Pending 任务或可重新提交的申请人才能获得写强度(editable / required),所有 只读上下文都被钳制为 visiblehidden 的值会从返回的 formData 中剥离, 没有任何可识别上下文的查看者什么都看不到——解析是 fail-closed 的。

设计器默认值

NodeData.ApplyTo 会把省略的设计器字段解析为导出的默认值,保证运行时和未触碰过的设计器控件一致:

常量
DefaultExecutionTypeExecutionManual
DefaultApprovalMethodApprovalParallel
DefaultPassRulePassAll
DefaultEmptyAssigneeActionEmptyAssigneeAutoPass
DefaultSameApplicantActionSameApplicantSelfApprove
DefaultConsecutiveApproverActionConsecutiveApproverNone
DefaultRollbackTypeRollbackPrevious
DefaultRollbackDataStrategyRollbackDataKeep
DefaultTimeoutActionTimeoutActionNone
DefaultCCTimingCCTimingAlways
DefaultHandleApprovalMethodApprovalSequential
DefaultHandlePassRulePassAny
DefaultUrgeCooldownMinutes30
DefaultTenantID"default"

流程定义(兼容 React Flow)

FlowDefinition 结构体兼容 React Flow 的 JSON 格式:

type FlowDefinition struct {
Nodes []NodeDefinition `json:"nodes"`
Edges []EdgeDefinition `json:"edges"`
}

每个 NodeDefinition 包含 Kind 和强类型的 Data 字段,框架会按 KindData 解析成对应结构(StartNodeDataApprovalNodeDataHandleNodeDataConditionNodeDataCCNodeDataEndNodeData)。

流程 JSON Wire Shape

deploy 会把流程定义当作完整快照处理。NodeDefinition.ParseData 根据 kind 选择对应的强类型 data 结构;未知 kind 返回 ErrUnknownNodeKind, 节点 data 的 JSON 解析失败会用 ErrNodeDataUnmarshal 包装。

类型JSON 字段
FlowDefinitionnodesedges
NodeDefinitionidkindpositiondataposition 包含 xy
EdgeDefinitionidsourcetargetsourceHandledata

只有条件节点的出边需要 sourceHandle,且它必须匹配某个分支 id。非条件节点的出边必须省略 sourceHandleEdgeDefinition.data 是设计器元数据,保存在版本 flowSchema 中;运行时流转使用 sourcetargetsourceHandle

节点 data 字段如下:

节点 data 类型JSON 字段
BaseNodeDatanamedescription;每种节点 data 都嵌入它
StartNodeData只有 base 字段
EndNodeData只有 base 字段
TaskNodeDataassigneesexecutionTypeemptyAssigneeActionfallbackUserIdsadminUserIdsisTransferAllowedisOpinionRequiredtimeoutHourstimeoutActiontimeoutNotifyBeforeHoursurgeCooldownMinutesccsfieldPermissions
ApprovalNodeDatabase 字段 + TaskNodeData 字段 + approvalMethodpassRulepassRatiosameApplicantActionconsecutiveApproverActionrollbackTyperollbackDataStrategyrollbackTargetKeysisRollbackAllowedisAddAssigneeAllowedaddAssigneeTypesisRemoveAssigneeAllowedisManualCcAllowed
HandleNodeDatabase 字段 + TaskNodeData 字段;未设置时部署会默认 approvalMethod = sequentialpassRule = any
CCNodeDatabase 字段 + ccsisReadConfirmRequiredfieldPermissions
ConditionNodeDatabase 字段 + branches

assignees 条目使用 kindidsformFieldsortOrderccs 条目使用 kindidsformFieldtiming。部署时这些嵌入数组会额外物化为 FlowNodeAssigneeFlowNodeCC 记录,不只是写入 FlowNode 行。

条件分支使用 idlabelconditionGroupsisDefaultpriority。 每个 conditionGroups 条目包含 conditions;每个 condition 使用 kindsubjectoperatorvalueexpression。字段条件也可以改为对明细表格的行做聚合: aggregatesum / count / avg)对 subject 指定的表格字段求值, column 指定要聚合的数字列(sum / avg 必填,count 禁止填写)——见 明细表格聚合

timeoutHourstimeoutNotifyBeforeHours 的单位是小时。 urgeCooldownMinutes 的单位是分钟;小于等于 0 时使用 30 分钟运行时默认值。rollbackTargetKeys 只在 rollbackType = specified 时校验,里面放的是节点 key,不是数据库节点 ID。 fieldPermissions 的语义见节点字段权限

表单 Schema 与派生字段

从 v0.38 起,表单定义在部署时一分为二:

  • FlowVersion.FormSchemaformSchema)是宿主自有的表单设计器文档, 在 deploy 时以 params.formSchema 提交,以语义等价的 JSON 存储和返回—— jsonb 列会规范化格式与键序,数字精度端到端保留(全程 json.RawMessage)。框架从不解释它。
  • FlowVersion.FormFieldsformFields)是部署时通过注入的 approval.FormSchemaParser 从该文档一次性派生出的扁平 []FormFieldDefinition 列表,是框架自身消费的唯一表单形状——用于 表单数据校验、storage-table DDL、聚合校验和字段权限解析。parser 升级 永远不影响已部署的版本。
type FormSchemaParser interface {
ParseFormFields(ctx context.Context, schema json.RawMessage) ([]FormFieldDefinition, error)
}

内置 parser 理解 vef-framework-react form-editor 文档;使用自有设计器的宿主 用 vef.ProvideApprovalFormSchemaParser(constructor) 整体替换。nil 或空 schema 产出零字段(没有表单的流程);parser 报错会中止部署。ctx 携带部署 请求的 deadline——执行 I/O 的宿主 parser 必须遵守它。

派生字段会在部署时校验(key 唯一、kind 已知、pattern 可编译、边界一致、 明细表单层)。每个 FormFieldDefinition 条目使用 keykindlabelplaceholderdefaultValueisRequiredoptionsvalidationpropssortOrdercolumnTypescalecolumns。每个 option 使用 labelvaluecolumns 定义 table 字段 (kindtable)的行结构:每一列本身也是一个 FormFieldDefinition,且不能再声明 自己的 columns——明细表格只能是单层。table 字段自身的 validation.minLength / maxLength 用于约束行数,isRequired 表示至少要有一行。

validation 支持 minLengthmaxLengthminmaxpatternmessage。提交的 formData 在 JSON 编码后最大 64 KiB,即使流程没有表单 schema 也会执行这个大小限制。有 schema 时,额外的表单 key 会被拒绝;必填字段会拒绝 缺失、null、空白字符串和空数组。inputtextareadate 字段必须是字符串, 可使用 minLengthmaxLengthpatternnumber 字段接受 JSON 数字, 可使用 minmaxselect 字段在配置了 options 时会校验标量或数组值是否 存在于选项中。upload 字段接受非空白字符串、非空 []string,或非空且每项都是非空白 字符串的数组。validation.message 只作为 pattern 不匹配时的自定义错误信息; 其他校验失败使用模块 i18n 消息。

流程模型与设计器枚举

公开包暴露的流程设计和持久化模型包括 FlowCategoryFlowFlowVersionFlowNodeFlowEdgeFlowInitiatorFlowNodeAssigneeFlowNodeCCFormFieldDefinitionFormSnapshotActionLogOperatorInfoUrgeRecord(结构化的 FormDefinition 包装在 v0.38 中被 移除——宿主文档是 opaque 的,框架侧只保留 FormFieldDefinition 这一种 形状)。流程版本状态使用 VersionStatusVersionDraftdraft)、VersionPublishedpublished)、 VersionArchivedarchived)。

其他流程设计器枚举:

枚举Wire values
InitiatorKinduserroledepartment
ExecutionTypemanualauto_passauto_reject
ConditionKindfieldexpression
CCKinduserroledepartmentform_field
CCTimingalwayson_approveon_reject
FieldKindinputtextareaselectnumberdateuploadtable
ColumnDataTypestringtextintegerdecimalbooleandatedatetimejson
Permissionvisibleeditablehiddenrequired

下一步:实例运行时 了解设计好的流程启动后会发生什么。