流程仿真功能设计¶
状态:后端已落地(2026-09-12,
java仓master);设计器弹窗已支持先选企业域再搜发起人。仿真单据自 2026-09-28 起不写入表单表,改放进程内缓存。
关联:[[流程仿真功能参考]]、[[流程相关规划]]、计划docs/superpowers/plans/2026-09-12-flow-simulation.md
说明:本文是本期实现依据。统计仿真、独立仿真库、测试用例回归见文末「非本期」。
1. 功能定位¶
流程仿真(又称「流程仿真测试」)是流程建模完成后、正式发布前的质量关口:在**不产生对业务可见待办和统计**的前提下,由设计人员完整走查一条流程,验证节点、出口条件、操作者配置、表单必填、节点/出口附加操作是否符合预期。
本期形态是**交互式走查**,不是按岗位时长/通过率批量跑实例的统计仿真。
生命周期位置:需求调研 → 分析设计 → 搭建 → 测试仿真 → 发布 → 监控分析 → 优化运维。
1.1 本期做¶
- 设计器内选发起人、按真实引擎走查一条单据。
- 到人工节点时,从该节点真实操作者配置算出的候选人中选处理人。
- 会签 / 或签 / 串签按引擎
passcondition执行。 - 会话级开关:校验必填、字段赋值、动作流。
- 流程运行时数据与正式数据同库同表,靠
ISSIMULATION隔离;仿真单据不写入TLK_*/LOG_*,放进程内缓存。测完保留到创建人删除,或缓存过期、被淘汰、进程重启。
1.2 本期不做¶
| 不做 | 原因 |
|---|---|
| 统计/批量仿真(处理时长、通过率、随机岗位) | 另一套引擎与指标,见《流程相关规划》 |
| 独立仿真 JDBC 数据源 | 走查不需要物理分库 |
| 独立菜单「流程仿真测试」 | 入口只放流程详情 |
| 测试用例、预期对比、通过/不通过报告 | 走查以人工核对为主 |
| 自由流 | 无固定节点操作者,无法按「到点选人」验证 |
| 转交、加签、撤回、终止(引擎终止) | 走查刚需是提交/驳回/回退 |
完整切换 WebUser 为被仿冒人 |
避免设计人员借仿真提权 |
| 未保存脏数据直接测 | 必须先保存,引擎跑的是已落盘定义 |
2. 实现方案¶
仿真会话包一层现有引擎。 设计器只负责任务编排和界面;流转仍走 DocumentProcess.doStartFlow / doFlow、StateMachine。中间增加仿真会话,写入仿真标记,并按会话开关控制必填与附加操作。
不采用:运行时 iframe 嵌表单(跨 designer/runtime,选人难嵌);也不自写仿真解释器(测的不是上线那套逻辑)。
3. 操作入口¶
设计时态 → 流程设计 → 流程详情,与【保存】并列【流程仿真测试】。
- 可见范围:能编辑该流程的人(与保存同一权限,不新增权限点)。
- 自由流:按钮可见,点击后提示「本期不支持自由流仿真」,不打开弹窗。
- 同一页提供 仿真记录 列表(见 4.3、5.3),不单开全局菜单。
4. 界面设计¶
4.1 配置弹窗「流程测试」¶
点击【流程仿真测试】:
- 若设计器有未保存修改:提示先保存;取消则不进入仿真。
- 保存成功(或无脏数据)后,弹出模态框。
弹窗结构:标题栏「流程测试」、三个可折叠分组、底部【开始测试】【取消】。
分组一:基本信息¶
| 项 | 控件 | 说明 |
|---|---|---|
| 工作流名称 | 只读 | 当前流程名称 |
| 选择工作流版本 | 下拉 | 见下方「版本」 |
版本(与现网能力对齐): 当前 BillDefiVO **没有**独立版本表,一份流程定义即当前已保存的 flow XML。本期下拉只有一项「当前已保存」。界面保留下拉,便于以后接多版本;**不要**写死「活动版本 V1」这类不存在的数据。开始测试时把当时的流程 XML 快照进 T_FLOWSTATERT.FLOWXML(现有字段),走查过程不读取别人中途保存的新定义。
分组二:选择发起人身份¶
弹窗**只选发起人**,不再为每个节点预置分组(分支在开始前未知)。先选企业域,再从该域用户中搜索。
| 项 | 控件 | 说明 |
|---|---|---|
| 企业域 | 下拉 | 当前登录人可见、且与本软件绑定(超级管理员无域时)的企业域。仅一项时自动选中 |
| 请选择发起人 | 人员选择 | 必须先选企业域;按姓名或账号搜索该域用户(人像 + 列表) |
橙色提示:浏览按钮等字段的可选数据范围,始终按当前登录身份计算,避免越权。
后续节点处理人在工作台「到达节点时」再选,且必须来自该节点操作者配置(见 6.2)。
分组三:执行设置¶
| 项 | 控件 | 默认 |
|---|---|---|
| 校验必填字段 | 开关 | 开 |
| 触发节点/出口附加操作 | 开关 | 开;开启后右侧 ☑字段赋值 ☑动作流,均默认勾 |
三者均为 本次会话级:发起、同意、驳回、回退的每一次提交都用开始时的值,工作台不再改。
- 总开关关:字段赋值、动作流都不执行。
- 总开关开:两个复选框分别控制。
与现网概念对应:
- 字段赋值:节点/出口脚本及动作里对文档字段的写入。
- 动作流:出口
Relation.action及节点上同类附加脚本/动作。
交互¶
- 【开始测试】:校验已选发起人 → 创建仿真会话与仿真实例 → 打开仿真工作台。
- 【取消】或「×」:关闭弹窗,不创建会话。
- 三个分组可折叠。
4.2 仿真工作台¶
设计器内**整页**(不是弹窗),不跳运行时待办中心。
| 区域 | 内容 |
|---|---|
| 左侧 | 流程图:已走 / 当前 / 未走节点区分 |
| 右侧 | 当前节点表单(走现有表单渲染,数据范围按登录人) |
| 顶栏 | 当前节点名、本节点仿真处理人;【提交/同意】【驳回】【回退】【结束本次仿真】 |
走查循环:
- 发起:右侧为开始节点表单,处理人 = 弹窗所选发起人。
- 提交成功后,若下一当前节点是人工节点:先弹出 选择处理人,名单 = 该节点操作者配置算出的候选人,必须选一人才能打开该节点表单。
- 算不出任何人:提示「操作者配置未匹配到处理人」,会话保持进行中,不自动任选。
- 或签(
PASS_CONDITION_OR):选一人处理并通过后,其余待办作废,节点完成。 - 汇签(
PASS_CONDITION_AND):同一节点反复「选人 → 处理」,直到全部(或引擎定义的通过条件)满足。 - 串签(
PASS_CONDITION_ORDERLY_AND):按顺序选下一个处理人。 - 自定义通过脚本(
PASS_CONDITION_SCRIPT):仍按引擎结果判断节点是否完成,工作台按返回的候选人继续选人。 - 驳回 / 回退:
FlowType.RUNNING2RUNNING_BACK,回到人工节点时再次从候选人中选人。 - 【结束本次仿真】:状态 = 已中止,关闭工作台。内存单据不因此清除,直到删除会话、缓存过期、容量淘汰或进程重启。
- 流程到达结束:状态 = 已结束,可只读查看后关闭。
本期工作台动作只有上述四个;不提供转交、加签、撤回。
4.3 仿真记录¶
流程详情下的列表来自 T_FLOW_SIMULATION,仅当前流程、当前用户创建的会话:
| 列 | 说明 |
|---|---|
| 单据摘要 / id | 仿真实例 |
| 发起人 | 仿真发起人姓名 |
| 状态 | 进行中 / 已结束 / 已中止 |
| 创建时间 | — |
| 操作 | 进行中:继续;已结束/已中止:只读打开;删除 |
删除:DELETE .../simulation/{sessionId} 调用 SimulationFlowServiceImpl.remove()。事务提交成功后从 SimulationDocumentCache 去掉该单据;并删除 T_FLOWSTATERT / T_ACTORRT / T_NODERT / 历史及仿真会话记录。表单表没有对应行,不删 TLK_*。删除失败回滚时缓存保留。
本期仅创建人可继续、删除自己的实例。
5. 数据存储与隔离¶
不设独立仿真库。流程运行时数据与正式数据**同库同表**,用 ISSIMULATION 隔离。仿真单据**不写入** TLK_* / LOG_*,放在进程内缓存。
**禁止**复用文档 ISTMP(那是未提交临时单)。
5.1 仿真单据缓存¶
doc.getSimulation() == true 时:
DocumentProcessBean.doCreateOrUpdate写入SimulationDocumentCache后返回,不访问表单表。doView/doViewWithOutCache先查该缓存,再查数据库。仿真启动、提交、流程图、历史都读这份内存单据。- 启动时若
DocumentLongId.ensure改写了主键,rekey删掉旧键并挂到新键,过期时间从新写入起算。
实现类:cn.myapps.core.runtime.dynaform.document.ejb.SimulationDocumentCache(Caffeine)。
| 项 | 值 |
|---|---|
| 过期 | expireAfterWrite 10 分钟。每次 put(含仿真提交写回)重新计时;只读不续期 |
| 容量 | maximumSize 1000,超出后按 Caffeine 容量策略淘汰 |
| 主动清除 | 删除仿真会话且事务提交成功后 remove(docId) |
| 进程 | 静态缓存,重启后全部消失 |
中止仿真、流程结束都不清缓存。超过 10 分钟没有再次写入、被容量淘汰或进程重启后,工作台再打开该会话读不到表单数据;T_FLOW_SIMULATION 与流程运行时行仍在。直接 SQL 查询 TLK_* 的脚本看不到仿真单据,内存中的 Document 仍带字段值。
AbstractDocument.simulation 只留在内存对象上。新建表单表、操作历史表的固定列不再包含 ISSIMULATION(ChangeLog.getFixedColumns()、单据 INSERT/UPDATE、DQL SYSTEM_FIELDS 均已去掉)。已有 TLK_* / LOG_* 上的该列不删除,单据读写也不再使用它。
5.2 流程实例标记¶
| 位置 | 字段 | 说明 |
|---|---|---|
T_FLOWSTATERT |
ISSIMULATION |
BIT,默认 0;仿真启动写入 1 |
T_FLOWHISTORY |
ISSIMULATION |
流程结束后实例迁入此表,必须仍能识别仿真 |
T_RELATIONHIS |
ISSIMULATION |
流转历史;仿真提交写入 1 |
T_FLOW_SIMULATION |
会话行 | 见 5.3 |
流程结束后现网会删 T_FLOWSTATERT、历史进 T_FLOWHISTORY。仿真记录列表靠会话表,不靠表单表上的标记。T_FLOWHISTORY.ISSIMULATION 仍用于结束后的流程查询隔离。待办排除仍用待办表 pen.ISSIMULATION。
5.3 仿真会话表 T_FLOW_SIMULATION¶
一次【开始测试】一行:
| 列 | 说明 |
|---|---|
| ID | 会话 id |
| APPLICATIONID / DOMAINID | 定位软件与域 |
| FLOWID / FORMID | 流程与表单 |
| DOC_ID | 仿真文档 id |
| FLOWSTATERT_ID | 开始时的流程实例 id(结束后可空) |
| INITIATOR_ID | 仿真发起人 |
| CREATOR_ID | 当前设计人员 |
| VALIDATE_REQUIRED | 校验必填 |
| TRIGGER_ACTIONS | 触发附加操作总开关 |
| FIELD_ASSIGN | 字段赋值 |
| ACTION_FLOW | 动作流 |
| STATUS | 0 进行中 / 1 已结束 / 2 已中止 |
| CREATED / LASTMODIFIED | — |
5.4 查询隔离(默认排除仿真)¶
仿真单据不在 TLK_* 中,表单内容查询不再追加 d.ISSIMULATION = 0。
以下仍按流程侧标记排除仿真:
- 待办
getPendingList:pen.ISSIMULATION = 0 OR pen.ISSIMULATION IS NULL - 抄送 / 协办 / 流程监控 / 流程干预
- 流程分析 / 报表统计(
T_FLOWSTATERT/T_FLOWHISTORY)
仿真记录列表查 T_FLOW_SIMULATION(当前流程 + 创建人),不查表单表。工作台按会话上的 DOC_ID 读内存单据。
5.5 通知¶
Notification 及待办到达、回退、催办、抄送、邮件/短信:若文档或流程实例为仿真,直接跳过发送。被仿冒人的正式待办中心看不到仿真任务。
5.6 子流程¶
子流程实例同样打 ISSIMULATION,不进正式待办、不发通知。工作台若当前令牌落到子流程人工节点,同样走「选候选人 → 处理」。
5.7 已有表补列:保存数据源时触发¶
流程运行时表缺 ISSIMULATION 时,不单独做手工 SQL 脚本,沿用现网「保存默认数据源 → 初始化 RT 表」:
DataSourceDesignTimeServiceImpl.save / saveOrUpdate → refreshRuntimeDataSourceAndInitTables(仅**默认数据源**)。
-
系统表(已有
initTables())
AbstractApplicationInitDAO.initTables()对比INIT_TABLEMAP与库表,缺列则AddColumnChange。模型里为T_FLOWSTATERT、T_FLOWHISTORY、T_RELATIONHIS增加ISSIMULATION;新建T_FLOW_SIMULATION。非默认数据源不跑此项(与现网一致)。 -
表单内容表 + 操作历史表
保存默认数据源后仍会对该应用全部表单调用FormTableProcess.createOrUpdateDynaTable(form, form),用于同步其它固定列。固定列模型已不含ISSIMULATION,因此**不会**给缺列的TLK_*/LOG_*补这一列,也**不会**删除已有列。未开「显示操作历史」、库中也没有LOG_*的表单:不新建历史表。
失败策略:单张表失败记日志、继续下一张,不回滚数据源保存本身(与应用激活时同步动态表的策略一致)。
6. 身份与选人¶
6.1 冒充范围¶
| 生效 | 不生效 |
|---|---|
流程发起人、ActorRT 操作者、流程日志/历史中的处理人;仿真单据作者/默认部门取所选发起人(摘要脚本等按发起人算) |
WebUser、组织权限、浏览框、视图过滤、字段可选数据范围 |
当前登录人始终是设计人员。这与弹窗橙色提示一致:走查重点是节点和出口,不是借用业务员数据权限。
限制(写进说明即可):若操作者脚本依赖「当前处理人部门」,而引擎脚本上下文仍是登录人,本期按登录人计算候选人。
6.2 候选人¶
- 发起人:弹窗先选企业域,再从该域人员中按姓名/账号搜索任选。
- 后续人工节点:
StateMachineUtil等现有逻辑,按节点namelist/ 部门 / 角色 /actorListScript计算。测试人**只能**从结果中选一个。 - 忽略节点配置、从全员任选:不做。
会签时,「选人」是从**尚未处理且仍在候选人集合中**的人里选,不是每次从全量重选已处理过的人。
7. 架构与数据流¶
流程详情 ──开始测试──► 仿真会话 T_FLOW_SIMULATION
│
▼
DocumentProcess.doStartFlow / doFlow
StateMachine(候选人、或签/会签/回退)
│
▼
仿真单据 SimulationDocumentCache
T_FLOWSTATERT.ISSIMULATION=1
│
┌───────────────┴───────────────┐
▼ ▼
运行时待办/报表(排除) 设计器工作台 / 仿真记录
7.1 一次提交¶
- 工作台提交,带会话 id、动作(同意/驳回/回退)、所选处理人 id、表单数据。
- 校验:会话进行中;当前用户 =
CREATOR_ID;处理人 ∈ 当前节点未处理候选人。 - 若
VALIDATE_REQUIRED:按该节点表单权限做必填校验(规则来自表单,数据范围仍按登录人)。 - 调用现有
doStartFlow/doFlow;操作者与日志写仿真处理人;WebUser仍是设计人员。 - 若
TRIGGER_ACTIONS关:跳过出口/节点action及字段写入类脚本。若开,则按FIELD_ASSIGN/ACTION_FLOW分别跳过。 - 返回:是否结束、当前节点、候选人、流程图状态。需选人则先选再渲染表单。
7.2 模块边界¶
| 模块 | 职责 |
|---|---|
| obpm-designer | 弹窗、工作台、仿真记录;只编排会话 |
| obpm-core | 会话、标记、查询排除、通知跳过、开关控制附加逻辑、选人校验 |
| obpm-runtime | 待办/监控/报表排除仿真;**不提供**仿真入口 |
7.3 设计器 API¶
前缀:/api/designtime/applications/{applicationId}/modules/{moduleId}/workflows/{flowId}/simulation
(实现上可再加 ${myapps.context-path.designer:} 前缀,默认空。)
鉴权:已登录;会话级写操作仅创建人。flowOption 只允许 80(提交/同意)和 81(回退);其它返回 400。SimulationException 返回 400,会话不存在或 path 的 flowId 与会话不一致返回 404。
| Method | Path | Body / 行为 |
|---|---|---|
| POST | /{applicationId}/modules/{moduleId}/workflows/{flowId}/simulation |
{initiatorId, validateRequired, triggerActions, fieldAssign, actionFlow} → 开始 |
| GET | /{applicationId}/modules/{moduleId}/workflows/{flowId}/simulation |
当前用户该流程会话列表 |
| GET | .../simulation/domains |
可选企业域 [{id, name}]。范围:设计人员已绑定域;若为空则取绑定本软件的企业域,再退回全部域 |
| GET | .../simulation/initiators |
Query:domainId(必填)、name(姓名或账号模糊)、pageNo、linesPerPage。未选域返回 400;域不在可选范围返回 400 |
| GET | /{applicationId}/modules/{moduleId}/workflows/{flowId}/simulation/{sessionId} |
当前态 |
| GET | .../simulation/{sessionId}/candidates |
候选人 |
| PUT | .../simulation/{sessionId}/actor |
{actorId} |
| POST | .../simulation/{sessionId}/submit |
{currNodeId, nextNodeIds, flowOption, comment, params} |
| POST | .../simulation/{sessionId}/abort |
结束本次仿真(STATUS=ABORTED,不删单据) |
| DELETE | .../simulation/{sessionId} |
删除会话及仿真单据 |
工作台左侧流程图走 监控只读接口(路径与 runtime 对齐,挂在设计器 ${myapps.context-path.designer:}/api/runtime 上;前端 contextPath 为 /designer/api):
| Method | Path | 行为 |
|---|---|---|
| GET | /api/runtime/{applicationId}/documents/{docId}/workflows/flowhistorys |
流程历史;Query content 按节点名/审批人/意见模糊过滤 |
| GET | /api/runtime/{applicationId}/documents/{docId}/workflows/{instanceId}/newflowchart |
带已走/当前节点标记的流程图 JSON |
当前态 JSON 至少包含:sessionId, status, docId, currentNodeId, currentActorId, validateRequired, triggerActions, fieldAssign, actionFlow, candidatesNeedSelect。
前端契约(页面不在本 Java 仓库):
- 流程详情与「保存」并列「流程仿真测试」;能编辑该流程则显示。
- 脏数据先提示保存。
- 弹窗:名称只读;版本下拉仅「当前已保存」;先选企业域,再选该域发起人;执行设置开关;开始/取消。
- 开始:
POST .../simulation,进入工作台(左流程图走newflowchart/flowhistorys,右表单)。 - 到人工节点:
GET candidates→ 选人PUT actor→ 打开表单。 - 按钮:提交
flowOption=80,回退81,结束POST abort。 - 仿真记录:
GET列表;进行中继续;已结束只读;删除DELETE。 - 橙色提示:浏览框按当前登录人。
8. 异常¶
| 情况 | 行为 |
|---|---|
| 未保存点开始后取消 | 不创建会话 |
| 自由流 | 提示不支持 |
| 未选企业域点开始 | 校验失败,留在弹窗 |
| 未选发起人点开始 | 校验失败,留在弹窗 |
| 候选人为空 | 提示配置错误,节点不前进 |
| 选了非候选人 | 拒绝提交 |
| 必填失败 | 留在当前表单 |
| 附加脚本失败 | 与正式运行相同:提交失败,节点不前进 |
| 会话已结束/已中止再提交 | 拒绝 |
| 非创建人操作会话 | 拒绝 |
| 走查中他人保存了流程 | 仍用开始时写入 FLOWXML 的快照 |
9. 测试要点¶
实现前先落失败用例,再写产品代码。
- 仿真单据不出现在
getPendingList、监控、默认视图。 - 仿真不调用
Notification发送。 - 下一节点只能从真实操作者结果中选;空候选人失败。
- 或签一人通过即完成节点;汇签未满足通过条件不能离开节点。
- 关必填可跳过必填;关附加操作则
Relation.action/ 字段写入不执行。 - 关「字段赋值」但开「动作流」(及相反)分别只跳过对应路径。
- 未保存取消不建会话;自由流拒绝;非创建人不能提交;结束后不能提交。
- 删除仿真实例后内存单据、待办、会话行均不在;
TLK_*无对应行。 - 仿真单据
getSimulation()==true时不落表单表;正式单据仍落库。ISTMP与仿真标记互不影响。 - 缓存:写入后 10 分钟过期,最多 1000 条;删除会话立即清除;中止仿真不清除。
10. 非本期(后续)¶
- 独立仿真数据源、历史基准导入、指标对比(《流程相关规划》)。
- 全局仿真菜单、测试用例保存与回归、通过/不通过报告。
- 流程定义多版本下拉真正可选历史版。
- 转交、加签、撤回;完整身份切换;自由流。
- 单独「流程测试」权限点。
11. 决策记录¶
| 决策 | 选择 |
|---|---|
| 产品形态 | 交互式走查,不是统计仿真 |
| 后续节点选人时机 | 到节点再选,弹窗只选发起人 |
| 数据隔离 | 流程运行时同库同表 + ISSIMULATION;仿真单据放 Caffeine 缓存,不写 TLK_* |
| 身份 | 只冒充流程处理人,数据范围按登录人 |
| 候选人 | 严格按节点操作者配置 |
| 走查界面 | 设计器内工作台(左流程图、右表单) |
| 测哪份定义 | 先保存;本期仅当前已保存定义(平台尚无版本表) |
| 会签/或签/串签 | 按真实引擎 passcondition |
| 动作 | 提交/同意、驳回、回退、结束本次仿真 |
| 测完数据 | 会话与流程运行时数据保留至创建人删除;内存单据另受 10 分钟过期与 1000 条容量限制 |
| 执行开关 | 整个会话每一次提交 |
| 入口 | 仅流程详情 |
| 引擎复用 | 会话包一层现有 doStartFlow / doFlow |
12. 落地情况(2026-09-12)¶
后端已合并 java 仓 master。设计器弹窗:先选企业域,再按姓名/账号搜索该域发起人。
| 位置 | 说明 |
|---|---|
obpm-core/.../workflow/simulation/ |
会话、开关、选人校验、SimulationFlowServiceImpl |
obpm-core/.../workflow/simulation/dao/JdbcSimulationSessionDAO.java |
T_FLOW_SIMULATION,各库共用 |
obpm-designer/.../workflow/simulation/controller/FlowSimulationController.java |
7.3 REST(含 domains、initiators?domainId=) |
obpm-designer/.../workflow/runtime/controller/WorkflowDesignTimeRuntimeController.java |
设计器侧 flowhistorys / newflowchart,路径对齐 runtime |
web/obpm-designer-vue3/.../ProcessSimulationDialog.vue |
流程测试弹窗:企业域 + 发起人 |
SimulationDocumentCache |
仿真单据;Caffeine,expireAfterWrite 10 分钟,maximumSize 1000 |
T_FLOWSTATERT / T_FLOWHISTORY / T_RELATIONHIS |
ISSIMULATION;保存默认数据源时补列 |
TLK_* / LOG_* |
新建表不再含 ISSIMULATION;已有列保留且不再读写 |
| 待办 / 默认查询 / 通知 | 排除仿真;Relation.action 受会话开关 |
实现细节:表单由模块下 Form.getOnActionFlow() 绑定流程;单据域取发起人;fieldAssign 暂无独立挂钩。单测:obpm-core 的 Simulation*Test。