流程路由条件与子流程¶
本章解决「流程走到岔路口时往哪走」一类问题:路径分支条件、经过路径时副作用、子流程选择与参数、自动节点动作,以及在脚本里判断当前是回退还是提交。配套的审批人计算见 流程审批人动态计算,流程历史与人工干预见 流程查询、审批历史与人工干预。
场景清单¶
| 场景 | 落点 / Label | 关键契约 |
|---|---|---|
| 路径进入条件分支 | WORKFLOW:RELATION_CONDITION(属性 condition) |
Boolean,true 走该路径 |
| 路径经过时更新字段 | WORKFLOW:RELATION_PASS_ACTION(属性 action) |
无返回值,副作用 |
| 选择 / 启动哪个子流程 | WORKFLOW:SUBFLOW_SELECT(属性 subflowScript) |
String,子流程 id |
| 子流程实例数(按用户数等) | WORKFLOW:SUBFLOW_INSTANCE(实例脚本) |
Number |
| 子流程参数传递 | WORKFLOW:SUBFLOW_PARAM(属性 paramPassingScript) |
与目标字段类型相符的值 |
| 自动节点动作 | WORKFLOW:AUTONODE_ACTION |
无强制,副作用 |
| 判断当前操作类型(回退/保存/提交) | 操作前置 / 值脚本 | 用 getParameter("_flowType") |
路径送出校验
WORKFLOW:RELATION_PASS_VALIDATE属于「校验」语义(失败返回提示 String、成功返回""),见 校验。
路径进入条件分支¶
业务目标:流程提交时根据当前表单字段值(金额、部门、类型等)动态决定走哪条路径(关联线),而不仅靠设计器里写死的字符串规则。
写在哪儿:流程域 → 流程设计器 → 双击关联线(路径)→ 路径进入条件脚本 → 值脚本。对应 .flow 属性 condition,Label WORKFLOW:RELATION_CONDITION。挂载点说明见 where-to-use/flow/flow-path.md。
触发时机与上下文:提交流程、计算路径分支时触发。可用环境变量 WebUser、CurrentDocument(即 getCurrentDocument() 拿到的当前文档)。
返回值契约:Boolean。true 表示走该路径;false 表示不走。与隐藏脚本语义方向一致(true 生效)。
示例代码:
// 按表单"部门"字段判断是否进入"总部审批"分支
(function () {
var doc = getCurrentDocument();
var deptName = doc.getItemValueAsString("部门");
return deptName === "总部";
})()
代码说明:
getCurrentDocument()取当前文档对象 →iscript-guid/api/doc.mddoc.getItemValueAsString("部门")按字段名取字符串值 →iscript-guid/functions/doc-functions.md- 字符串比较用
===(不要写"总部".equals(deptName),原因见 GraalVM 差异)
变体与扩展:
// 金额 > 10000 走"高管复审"分支
(function () {
var amount = getItemValueAsString("金额");
var n = Number(amount);
return !isNaN(n) && n > 10000;
})()
// 当前用户为"风控"角色时强制走风控分支
(function () {
var user = getWebUser();
var roles = user.getRoleList(); // Collection<UserRole>
if (roles != null) {
for (var iter = roles.iterator(); iter.hasNext();) {
var role = iter.next();
if (role.getName() === "风控") {
return true;
}
}
}
return false;
})()
常见坑:
- 返回的是
Boolean,不是字符串"true";直接return true,不要写return "true"。 - 多条关联线的进入条件若同时为
true,平台会按设计器内部顺序选其一;若需"互斥分支",请确保条件之间覆盖完整(如金额>10000与金额<=10000)。 - 字段值比较务必用
===(详见 GraalVM 差异);旧示例里的==在与数字混用时易出隐式转换。 - 路径分支脚本与"路径送出校验"是两件事——前者定方向,后者定能否送出。
路径经过时更新字段¶
业务目标:流程经过某条关联线(路径)时,自动给表单写一个字段值(如把"审批状态"置为"已审批"),无需等到下一节点打开表单。
写在哪儿:流程域 → 流程设计器 → 双击关联线(路径)→ 路径执行脚本 → 值脚本。对应 .flow 属性 action,Label WORKFLOW:RELATION_PASS_ACTION。挂载点说明见 where-to-use/flow/flow-path.md。
触发时机与上下文:流程提交经过该路径时触发(即在路径条件成立之后、目标节点收到之前)。可用 WebUser、CurrentDocument。
返回值契约:无强制(纯副作用脚本)。建议用 IIFE 包装但不返回。
示例代码:
// 路径经过时把"审批状态"字段置为"已审批"
(function () {
var process = getDocumentProcess();
var doc = getCurrentDocument();
doc.findItem("审批状态").setValue("已审批");
process.doUpdate(doc); // 必须落库,否则只改内存
})()
代码说明:
getDocumentProcess()取文档操作对象 →iscript-guid/functions/curdoc-functions.mddoc.findItem("审批状态").setValue(...)修改字段值 →iscript-guid/functions/curdoc-functions.mdprocess.doUpdate(doc)持久化,等价于保存动作 →iscript-guid/functions/curdoc-functions.md
变体与扩展:
// 经过路径时记录最近处理人和时间
(function () {
var doc = getCurrentDocument();
var user = getWebUser();
doc.findItem("最近处理人").setValue(user.getName());
doc.findItem("最近处理时间").setValue(format(new Date(), "yyyy-MM-dd HH:mm:ss"));
getDocumentProcess().doUpdate(doc);
})()
常见坑:
- 只调
setValue、忘记doUpdate,是最常见错误——内存里改了但库里没变。 - 这是在路径经过时副作用,**不要**用它做"是否允许送出"的判断;后者用
WORKFLOW:RELATION_PASS_VALIDATE(见 表单与导入校验)。 - 若字段较多或要做复杂联动,建议改在目标节点的"操作后置"或"审批人脚本"里写,更可控。
选择 / 启动哪个子流程¶
业务目标:主流程到达"子流程节点"时,按当前表单数据动态决定要启动哪个子流程模板(如技术部走"技术子流程"、其它部门走"普通子流程")。
写在哪儿:流程域 → 流程设计器 → 子流程节点 → 启动子流程脚本。对应 .flow 属性 subflowScript,Label WORKFLOW:SUBFLOW_SELECT。挂载点说明见 where-to-use/flow/flow-node.md。
触发时机与上下文:主流程到达子流程节点、即将启动子流程时触发。可用 WebUser、CurrentDocument。
返回值契约:String,子流程模板 id(即设计器里的"流程 id")。返回空或 null 会按节点默认配置走。
示例代码:
// 根据部门名走不同子流程
(function () {
var deptId = getItemValueAsString("部门");
var process = getDepartmentProcess();
var dept = process.doView(deptId);
var techSubFlowId = "技术子流程实例id"; // 设计器中复制
var normalSubFlowId = "普通子流程实例id";
if (dept != null && dept.getName() === "技术部") {
return techSubFlowId;
}
return normalSubFlowId;
})()
代码说明:
getItemValueAsString("部门")取部门字段值 →iscript-guid/functions/doc-functions.mdgetDepartmentProcess()/process.doView(id)取部门对象 →iscript-guid/functions/java-class-functions.md- 与"启动子流程脚本"(Label
WORKFLOW:SUBFLOW_STARTUPSCRIPT)配套:前者返回选哪个流程模板,后者在启动动作发生时执行(如做日志、初始化)。两者常共用一段逻辑。
变体与扩展:
// 按表单"金额"是否超过阈值选择子流程
(function () {
var amount = Number(getItemValueAsString("金额") || "0");
return amount > 50000 ? "大额子流程id" : "常规子流程id";
})()
常见坑:
- 返回值是**子流程模板 id**(设计器里看到的字符串),不是流程实例 id,也不是表单名。
- 比较部门对象时不要直接
dept == "技术部"(旧素材这样写不严谨,比较的是对象);应取dept.getName()后用===比较。 - 子流程模板 id 在不同环境(开发/生产)可能不同,建议放配置项或常量,避免硬编码到生产环境失配。
子流程实例数(按用户数等动态)¶
业务目标:主流程到子流程节点时,按表单里"用户"字段的人数(或其它计数)启动多个并行的子流程实例——每个用户一个独立实例。
写在哪儿:流程域 → 流程设计器 → 子流程节点 → 启动 → 实例脚本(属性同 subflowScript 节点的实例数次级脚本)。挂载点说明见 where-to-use/flow/flow-node.md。
触发时机与上下文:启动子流程时触发。可用 WebUser、CurrentDocument。
返回值契约:Number,要启动的子流程实例数(必须 ≥ 1)。
示例代码:
// 表单"用户"字段以";"分隔,按人数启动 N 个子流程实例
(function () {
var userIds = getItemValueAsString("用户");
var arr = userIds != null && userIds !== "" ? userIds.split(";") : [];
var num = arr.length > 0 ? arr.length : 1;
return num;
})()
代码说明:
String.prototype.split(";")是 JS 原生方法;.length是 JS 数组属性(**不要**写成.size(),详见 GraalVM 差异)。getItemValueAsString→iscript-guid/functions/doc-functions.md
变体与扩展:
// 按"金额"梯度决定实例数(这里仅示意,业务上罕用)
(function () {
var amount = Number(getItemValueAsString("金额") || "0");
if (amount > 100000) return 3;
if (amount > 10000) return 2;
return 1;
})()
常见坑:
- 返回
0或负数会让流程引擎异常;用arr.length > 0 ? arr.length : 1兜底。 - 表单字段值为空时,
"".split(";")在 JS 里会得到[""](长度为 1),需先判空再 split,否则会启动 1 个无意义实例。 -「子流程实例数」与「子流程实例序号」是两个概念——前者在主流程启动时定个数,后者是子流程自身查询自己是第几个(见common-script-collection/master-sub-flow-instance.md,通过doc.getParent()+ SQLorder by created遍历匹配当前文档 id)。
子流程参数传递¶
业务目标:主流程启动子流程时,给子流程某字段填一个初始值(如把主流程的"申请人"传给子流程的"申请人"字段,或扩展字段值)。
写在哪儿:流程域 → 流程设计器 → 子流程节点 → 参数传递脚本。对应 .flow 属性 paramPassingScript,Label WORKFLOW:SUBFLOW_PARAM。挂载点说明见 where-to-use/flow/flow-node.md。
触发时机与上下文:启动子流程时触发。可用 WebUser、CurrentDocument。
返回值契约:与目标字段类型相符的值(Object)。返回值会作为子流程对应字段的初始值。
示例代码:
// 把主流程"用户"字段对应的拓展字段值传给子流程
(function () {
var doc = getCurrentDocument();
var userId = doc.getItemValueAsString("用户");
var user = getUserById(userId);
if (user != null) {
return user.getField4(); // 用户拓展字段4的值
}
return userId;
})()
代码说明:
getCurrentDocument()→iscript-guid/api/doc.mdgetUserById(userId)按 id 取用户对象 →iscript-guid/api/usercenter.mduser.getField4()取用户扩展字段(产品约定的 User VO 方法)
变体与扩展:
// 把主流程的流水号 + 当前时间一起以 JSON 字符串传给子流程的"备注"字段
(function () {
var doc = getCurrentDocument();
var sn = doc.getItemValueAsString("流水号");
return "SN=" + sn + ";T=" + format(new Date(), "yyyy-MM-dd HH:mm:ss");
})()
常见坑:
- 返回值的类型必须能塞进目标字段(字符串字段返回字符串、日期字段返回日期);类型不符会落库为
null或抛错。 - 不要在此脚本里
doUpdate当前文档——这是参数脚本,不是副作用脚本。
自动节点动作¶
业务目标:流程走到"自动节点"(无人工审批、自动执行后流向下一节点)时,执行一段自定义动作(如打日志、调外部接口、做计算后写字段)。
写在哪儿:流程域 → 流程设计器 → 自动节点 → 脚本。对应 Label WORKFLOW:AUTONODE_ACTION。
触发时机与上下文:流程流转到自动节点时触发。可用 WebUser、CurrentDocument。
返回值契约:无强制(副作用脚本)。
示例代码:
(function () {
var doc = getCurrentDocument();
var user = getWebUser();
// 写日志(println 输出到平台日志)
println("auto node hit: doc=" + doc.getId() + " by " + user.getName());
})()
代码说明:
println(...)输出到平台日志,调试常用。- 自动节点的"送出时机"另由
auditDateTimeScript等节点时间脚本控制(见iscript-usage/flow.md中"自动节点送出时机"示例)。
变体与扩展:
// 自动节点:根据金额字段写"复审标记"
(function () {
var doc = getCurrentDocument();
var amount = Number(doc.getItemValueAsString("金额") || "0");
if (amount > 50000) {
doc.findItem("复审标记").setValue("需复审");
getDocumentProcess().doUpdate(doc);
}
})()
常见坑:
- 自动节点**没有人工干预**——脚本里的异常不会有人看到,建议用
try/catch包裹关键逻辑并把错误println到日志。 auditDateTimeScript用于控制自动节点的提交时间,常用parseDate解析表单日期字段。注意源素材里写法v.trim().length(属性)是 GraalVM 合规的(详见 GraalVM 差异),不要照搬某些旧示例的.length()。
判断当前操作类型(回退 / 保存 / 提交)¶
业务目标:在保存前、提交前、回退前等脚本里区分"当前用户在做什么操作",从而走不同分支(如回退时清空"审批状态"、提交时记录提交时间)。
写在哪儿:表单域 → 表单 → 控件或表单的"保存前/提交前/回退前脚本"(操作前后置),也可用于任意值脚本。无固定 Label(取决于挂在哪),核心是用 getParameter 取平台注入参数。
触发时机与上下文:保存/提交/暂存/回退前触发(具体看挂在哪个动作上)。可用 WebUser、CurrentDocument、getParameter("_flowType")、getParameter("_attitude")、getParameter("_operation")。
返回值契约:依挂载点而定(值脚本/操作前置的契约见 前言)。这里只讨论"如何取参数判断操作类型"。
示例代码:
// 区分回退 vs 保存/提交/暂存;并在回退时把"备注"写到"回退原因"字段
(function () {
var flowType = getParameter("_flowType");
var doc = getCurrentDocument();
if (flowType === "81") {
// 回退
var remark = getParameter("_attitude"); // 操作备注/意见
doc.findItem("回退原因").setValue(remark);
return "已记录回退原因";
}
if (flowType === "80") {
// 暂存 / 保存 / 提交(三者都会返回 80)
doc.findItem("最后操作时间").setValue(format(new Date(), "yyyy-MM-dd HH:mm:ss"));
return "";
}
return "其它操作类型:" + flowType;
})()
代码说明:
getParameter(name)取平台注入参数 →iscript-guid/functions/system-functions.mdgetParameter("_flowType"):"81"= 回退;"80"= 暂存/保存/提交(三种操作**都会**返回80,无法用它进一步细分,需结合_operation等参数或挂载到更精确的位置)getParameter("_attitude"):用户填写的审批意见/备注(可能为空,使用前需判空)format(date, pattern)格式化日期 →iscript-guid/functions/date-functions.md
变体与扩展:
// 仅在回退时执行副作用(写日志),其它操作直接放行
(function () {
if (getParameter("_flowType") === "81") {
var doc = getCurrentDocument();
println("rollback: " + doc.getId() + " reason=" + getParameter("_attitude"));
}
return "";
})()
// 取并清洗 _attitude(参考 common-script-collection/get-form-current-operation)
(function () {
var remark = getParameter("_attitude");
if (remark != null && remark.trim().length > 0) { // length 是属性
return remark.trim();
}
return "(无备注)";
})()
常见坑:
_flowType不能区分"保存"和"提交"——两者都返回"80"。需要细分时挂到不同的"保存前/提交前"位置,或结合_operation等参数;详见common-script-collection/get-form-current-operation.md的说明。_attitude在非审批场景可能为null,使用前必须判空;remark.trim().length(属性)不要写成.length()方法,详见 GraalVM 差异。- 比较参数值用
=== "81"(字符串字面量比较),不要用.equals(...)。
相关场景¶
- 路径送出校验、字段非空/格式校验 → 表单与导入校验
- 审批人动态计算、协办/抄送人 → 流程审批人动态计算
- 流程回退、强制终止、审批历史、干预 → 流程查询、审批历史与人工干预
- 主子流程实例序号查询(子流程知道自己第几个) →
common-script-collection/master-sub-flow-instance.md - 获取当前操作类型的原始示例 →
common-script-collection/get-form-current-operation.md