211 lines
8.0 KiB
Java
211 lines
8.0 KiB
Java
package com.yunhai.flowcenter.engine;
|
||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
import com.yunhai.platform.apistd.BizException;
|
||
import com.yunhai.platform.utils.exception.ErrorCode;
|
||
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* 流程定义 JSON 模型(概览 §5.1 规范,一期 5 节点)。
|
||
* 序列化/反序列化统一走 {@link #parse(ObjectMapper, String)},未知属性忽略(向前兼容)。
|
||
*/
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public class FlowDefModel {
|
||
|
||
/** 节点类型(一期 5 种,二期按需扩展 NOTIFY/CONDITION) */
|
||
public static final String TYPE_START = "START";
|
||
public static final String TYPE_END = "END";
|
||
public static final String TYPE_APPROVAL = "APPROVAL";
|
||
public static final String TYPE_PARALLEL_SPLIT = "PARALLEL_SPLIT";
|
||
public static final String TYPE_PARALLEL_JOIN = "PARALLEL_JOIN";
|
||
/** 抄送节点(2026-09-23 二期第一批):瞬时放行,逐人落 flw_cc + CC_SENT,不建任务不阻塞 */
|
||
public static final String TYPE_NOTIFY = "NOTIFY";
|
||
/**
|
||
* 条件分支节点(2026-09-24 二期第二批 §4):激活时同步调外部判断服务(flw_router 注册,
|
||
* routerKey 寻址)按返回出口名选路;失败挂起待管理端重跑,禁止配默认出口。
|
||
* 出边=互斥分支,name 必填且唯一(出口名即判断接口的 outlets 契约)。
|
||
*/
|
||
public static final String TYPE_CONDITION = "CONDITION";
|
||
|
||
/** CONDITION 判断方式(2026-10-10 四议题):外部判断服务(缺省=旧口径,routerKey 寻址) */
|
||
public static final String JUDGE_MODE_SERVICE = "SERVICE";
|
||
/** CONDITION 判断方式:内置表达式引擎(出边 condition 即 AviatorScript 表达式) */
|
||
public static final String JUDGE_MODE_EXPRESSION = "EXPRESSION";
|
||
|
||
/** 会签模式 */
|
||
public static final String COUNTERSIGN_ALL = "ALL";
|
||
public static final String COUNTERSIGN_ANY = "ANY";
|
||
|
||
/** 拒绝策略(一期仅 END_REJECTED) */
|
||
public static final String REJECT_END_REJECTED = "END_REJECTED";
|
||
|
||
/** 办理人类型(概览 §六:一期 DEPT_KEY/USERS;SUBMITTER_DEPT/FORM_FIELD 二期) */
|
||
public static final String ASSIGNEE_DEPT_KEY = "DEPT_KEY";
|
||
public static final String ASSIGNEE_USERS = "USERS";
|
||
|
||
/** 发起时指派选择模式:SINGLE=恰 1 人;MULTI=多选(min~max) */
|
||
public static final String ASSIGN_MODE_SINGLE = "SINGLE";
|
||
public static final String ASSIGN_MODE_MULTI = "MULTI";
|
||
|
||
public String code;
|
||
public String name;
|
||
public String start;
|
||
public List<Node> nodes;
|
||
public List<Edge> edges;
|
||
public Settings settings;
|
||
/**
|
||
* 画布坐标(nodeId → 左上角),仅前端设计器/查看器复用;引擎不消费。
|
||
* 缺失时前端自行做自动布局,故可选、可空。
|
||
*/
|
||
public Map<String, Layout> layout;
|
||
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class Node {
|
||
public String id;
|
||
public String type;
|
||
public String name;
|
||
public Assignee assignee;
|
||
/** NOTIFY 抄送接收人(复用 Assignee 结构:DEPT_KEY/USERS 两档,解析走 AssigneeResolver) */
|
||
public Assignee receivers;
|
||
/** CONDITION 判断服务注册键(flw_router.router_key,发布期校验须已注册且启用) */
|
||
public String routerKey;
|
||
/** CONDITION 判断方式:SERVICE(缺省,routerKey 外部服务)|EXPRESSION(出边 condition 表达式) */
|
||
public String judgeMode;
|
||
|
||
/** 是否表达式判断(null 视作 SERVICE,旧 contentJson 无此字段天然走服务口径) */
|
||
public boolean expressionJudge() {
|
||
return JUDGE_MODE_EXPRESSION.equals(judgeMode);
|
||
}
|
||
/** ALL(默认)|ANY */
|
||
public String countersign;
|
||
/** PARALLEL_JOIN 汇聚条件,一期仅 ALL */
|
||
public String waitFor;
|
||
/** 办理时限小时数(自然时间,任务 arrive_time + N 小时;可空) */
|
||
public Integer deadlineHours;
|
||
/** 发起时指派策略(2026-10-13 方案):存在即允许指派;缺省/缺失=不允许(向前兼容) */
|
||
public AssignPolicy assignPolicy;
|
||
}
|
||
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class Assignee {
|
||
/** DEPT_KEY | USERS */
|
||
public String type;
|
||
public String deptKey;
|
||
public List<Integer> userIds;
|
||
}
|
||
|
||
/** 发起时指派策略:mode 必填(容错 null=MULTI);min/max 可空(缺省 1/候选全量数);required 仅子应用 UI 用 */
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class AssignPolicy {
|
||
/** SINGLE | MULTI */
|
||
public String mode;
|
||
public Integer min;
|
||
public Integer max;
|
||
public Boolean required;
|
||
}
|
||
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class Edge {
|
||
public String from;
|
||
public String to;
|
||
/**
|
||
* 出边名(按钮文案)。单出口可空;多出口节点必填且同节点内唯一,
|
||
* 办理人在办理页选它决定走哪条边(见 {@link #edgeByName})。
|
||
*/
|
||
public String name;
|
||
/** 出口条件(二期条件自动路由预留;一期只校验透传,引擎不消费) */
|
||
public String condition;
|
||
}
|
||
|
||
/** 画布坐标(前端设计器专用) */
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class Layout {
|
||
public Integer x;
|
||
public Integer y;
|
||
}
|
||
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class Settings {
|
||
public String rejectPolicy = REJECT_END_REJECTED;
|
||
public Urge urge = new Urge();
|
||
}
|
||
|
||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||
public static class Urge {
|
||
public Boolean enabled = true;
|
||
public Integer intervalHours = 24;
|
||
public Integer maxTimes = 3;
|
||
}
|
||
|
||
public static FlowDefModel parse(ObjectMapper objectMapper, String json) {
|
||
try {
|
||
FlowDefModel model = objectMapper.readValue(json, FlowDefModel.class);
|
||
if (model == null || model.nodes == null || model.edges == null) {
|
||
throw new BizException(ErrorCode.PARAM_ERROR, "流程定义 JSON 缺少 nodes/edges");
|
||
}
|
||
if (model.settings == null) {
|
||
model.settings = new Settings();
|
||
}
|
||
return model;
|
||
} catch (BizException e) {
|
||
throw e;
|
||
} catch (Exception e) {
|
||
throw new BizException(ErrorCode.PARAM_ERROR, "流程定义 JSON 不合法:" + e.getMessage());
|
||
}
|
||
}
|
||
|
||
public Node node(String nodeId) {
|
||
for (Node n : nodes) {
|
||
if (n.id != null && n.id.equals(nodeId)) {
|
||
return n;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public Node nodeById(String nodeId) {
|
||
Node n = node(nodeId);
|
||
if (n == null) {
|
||
throw new BizException(ErrorCode.PARAM_ERROR, "流程定义缺少节点:" + nodeId);
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/** 出边集合(保持定义顺序) */
|
||
public List<Edge> outEdges(String nodeId) {
|
||
java.util.List<Edge> out = new java.util.ArrayList<>();
|
||
for (Edge e : edges) {
|
||
if (nodeId != null && nodeId.equals(e.from)) {
|
||
out.add(e);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
public List<Edge> inEdges(String nodeId) {
|
||
java.util.List<Edge> in = new java.util.ArrayList<>();
|
||
for (Edge e : edges) {
|
||
if (nodeId != null && nodeId.equals(e.to)) {
|
||
in.add(e);
|
||
}
|
||
}
|
||
return in;
|
||
}
|
||
|
||
/** 按出边名取边(多出口节点路由用);未命中返回 null */
|
||
public Edge edgeByName(String fromNodeId, String name) {
|
||
if (name == null) {
|
||
return null;
|
||
}
|
||
for (Edge e : outEdges(fromNodeId)) {
|
||
if (name.equals(e.name)) {
|
||
return e;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|