跳转至

流程仿真功能设计

状态:后端已落地(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 配置弹窗「流程测试」

点击【流程仿真测试】:

  1. 若设计器有未保存修改:提示先保存;取消则不进入仿真。
  2. 保存成功(或无脏数据)后,弹出模态框。

弹窗结构:标题栏「流程测试」、三个可折叠分组、底部【开始测试】【取消】。

分组一:基本信息

项 控件 说明
工作流名称 只读 当前流程名称
选择工作流版本 下拉 见下方「版本」

版本(与现网能力对齐): 当前 BillDefiVO **没有**独立版本表,一份流程定义即当前已保存的 flow XML。本期下拉只有一项「当前已保存」。界面保留下拉,便于以后接多版本;**不要**写死「活动版本 V1」这类不存在的数据。开始测试时把当时的流程 XML 快照进 T_FLOWSTATERT.FLOWXML(现有字段),走查过程不读取别人中途保存的新定义。

分组二:选择发起人身份

弹窗**只选发起人**,不再为每个节点预置分组(分支在开始前未知)。先选企业域,再从该域用户中搜索。

项 控件 说明
企业域 下拉 当前登录人可见、且与本软件绑定(超级管理员无域时)的企业域。仅一项时自动选中
请选择发起人 人员选择 必须先选企业域;按姓名或账号搜索该域用户(人像 + 列表)

橙色提示:浏览按钮等字段的可选数据范围,始终按当前登录身份计算,避免越权。

后续节点处理人在工作台「到达节点时」再选,且必须来自该节点操作者配置(见 6.2)。

分组三:执行设置

项 控件 默认
校验必填字段 开关 开
触发节点/出口附加操作 开关 开;开启后右侧 ☑字段赋值 ☑动作流,均默认勾

三者均为 本次会话级:发起、同意、驳回、回退的每一次提交都用开始时的值,工作台不再改。

  • 总开关关:字段赋值、动作流都不执行。
  • 总开关开:两个复选框分别控制。

与现网概念对应:

  • 字段赋值:节点/出口脚本及动作里对文档字段的写入。
  • 动作流:出口 Relation.action 及节点上同类附加脚本/动作。

交互

  • 【开始测试】:校验已选发起人 → 创建仿真会话与仿真实例 → 打开仿真工作台。
  • 【取消】或「×」:关闭弹窗,不创建会话。
  • 三个分组可折叠。

4.2 仿真工作台

设计器内**整页**(不是弹窗),不跳运行时待办中心。

区域 内容
左侧 流程图:已走 / 当前 / 未走节点区分
右侧 当前节点表单(走现有表单渲染,数据范围按登录人)
顶栏 当前节点名、本节点仿真处理人;【提交/同意】【驳回】【回退】【结束本次仿真】

走查循环:

  1. 发起:右侧为开始节点表单,处理人 = 弹窗所选发起人。
  2. 提交成功后,若下一当前节点是人工节点:先弹出 选择处理人,名单 = 该节点操作者配置算出的候选人,必须选一人才能打开该节点表单。
  3. 算不出任何人:提示「操作者配置未匹配到处理人」,会话保持进行中,不自动任选。
  4. 或签(PASS_CONDITION_OR):选一人处理并通过后,其余待办作废,节点完成。
  5. 汇签(PASS_CONDITION_AND):同一节点反复「选人 → 处理」,直到全部(或引擎定义的通过条件)满足。
  6. 串签(PASS_CONDITION_ORDERLY_AND):按顺序选下一个处理人。
  7. 自定义通过脚本(PASS_CONDITION_SCRIPT):仍按引擎结果判断节点是否完成,工作台按返回的候选人继续选人。
  8. 驳回 / 回退:FlowType.RUNNING2RUNNING_BACK,回到人工节点时再次从候选人中选人。
  9. 【结束本次仿真】:状态 = 已中止,关闭工作台。内存单据不因此清除,直到删除会话、缓存过期、容量淘汰或进程重启。
  10. 流程到达结束:状态 = 已结束,可只读查看后关闭。

本期工作台动作只有上述四个;不提供转交、加签、撤回。

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(仅**默认数据源**)。

  1. 系统表(已有 initTables())
    AbstractApplicationInitDAO.initTables() 对比 INIT_TABLEMAP 与库表,缺列则 AddColumnChange。模型里为 T_FLOWSTATERT、T_FLOWHISTORY、T_RELATIONHIS 增加 ISSIMULATION;新建 T_FLOW_SIMULATION。非默认数据源不跑此项(与现网一致)。

  2. 表单内容表 + 操作历史表
    保存默认数据源后仍会对该应用全部表单调用 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 一次提交

  1. 工作台提交,带会话 id、动作(同意/驳回/回退)、所选处理人 id、表单数据。
  2. 校验:会话进行中;当前用户 = CREATOR_ID;处理人 ∈ 当前节点未处理候选人。
  3. 若 VALIDATE_REQUIRED:按该节点表单权限做必填校验(规则来自表单,数据范围仍按登录人)。
  4. 调用现有 doStartFlow / doFlow;操作者与日志写仿真处理人;WebUser 仍是设计人员。
  5. 若 TRIGGER_ACTIONS 关:跳过出口/节点 action 及字段写入类脚本。若开,则按 FIELD_ASSIGN / ACTION_FLOW 分别跳过。
  6. 返回:是否结束、当前节点、候选人、流程图状态。需选人则先选再渲染表单。

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 仓库):

  1. 流程详情与「保存」并列「流程仿真测试」;能编辑该流程则显示。
  2. 脏数据先提示保存。
  3. 弹窗:名称只读;版本下拉仅「当前已保存」;先选企业域,再选该域发起人;执行设置开关;开始/取消。
  4. 开始:POST .../simulation,进入工作台(左流程图走 newflowchart / flowhistorys,右表单)。
  5. 到人工节点:GET candidates → 选人 PUT actor → 打开表单。
  6. 按钮:提交 flowOption=80,回退 81,结束 POST abort。
  7. 仿真记录:GET 列表;进行中继续;已结束只读;删除 DELETE。
  8. 橙色提示:浏览框按当前登录人。

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。