This commit is contained in:
那些花儿
2025-07-28 10:48:38 +08:00
parent 030cf7cc4c
commit 3b51ed7a51
450 changed files with 82310 additions and 1213 deletions
@@ -2,6 +2,7 @@ package com.budwk.app;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.sys.services.SysTaskService;
import com.budwk.app.task.services.TaskPlatformService;
import com.budwk.app.web.commons.auth.satoken.SaTokenContextImpl;
@@ -23,9 +24,7 @@ import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Mirror;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.ChainBy;
import org.nutz.mvc.annotation.Encoding;
import org.nutz.mvc.annotation.Localization;
import org.nutz.mvc.annotation.*;
import org.quartz.Scheduler;
import javax.management.MBeanServer;
@@ -81,6 +80,7 @@ public class MainLauncher {
init_task();
init_auth();
ioc.get(Globals.class);
ioc.get(FlowEngine.class);
}
public void init_auth() {
@@ -150,11 +150,9 @@ public class MainLauncher {
try {
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("com.alibaba.druid:type=MockDriver");
if (mbeanServer.isRegistered(objectName))
mbeanServer.unregisterMBean(objectName);
if (mbeanServer.isRegistered(objectName)) mbeanServer.unregisterMBean(objectName);
objectName = new ObjectName("com.alibaba.druid:type=DruidDriver");
if (mbeanServer.isRegistered(objectName))
mbeanServer.unregisterMBean(objectName);
if (mbeanServer.isRegistered(objectName)) mbeanServer.unregisterMBean(objectName);
} catch (Exception ex) {
}
}
@@ -85,10 +85,11 @@ public interface BaseService<T> {
/**
* 自定义SQL查询VO对象
*
* @param sql
* @param clazz
* @return 味大无需多盐
* @param <C>
* @return 味大无需多盐
*/
<C> C fetchVO(Sql sql, Class<C> clazz);
@@ -672,7 +673,6 @@ public interface BaseService<T> {
*/
<C> Pagination<C> listPageVO(PageForm pageForm, Sql sql, Class<C> clazz);
/**
* 自定义查询,并返回当前实体类对象
*
@@ -21,6 +21,7 @@ import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.service.EntityService;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -895,15 +896,32 @@ public class BaseServiceImpl<T> extends EntityService<T> implements BaseService<
@Override
public <C> Pagination<C> listPageVO(PageForm pageForm, Sql sql, Class<C> clazz) {
// Integer pageNumber = pageForm.getPageNumber();
// Integer pageSize = pageForm.getPageSize();
// Pager pager = this.dao().createPager(pageNumber, pageSize);
// pager.setRecordCount((int) Daos.queryCount(this.dao(), sql));// 记录数需手动设置
// sql.setPager(pager);
// sql.setCallback(new QueryVoCallback(clazz));
// this.dao().execute(sql);
// List<C> list = sql.getList(clazz);
// return new Pagination<>(pageNumber, pageSize, pager.getRecordCount(), list);
Integer pageNumber = pageForm.getPageNumber();
Integer pageSize = pageForm.getPageSize();
Pager pager = this.dao().createPager(pageNumber, pageSize);
pager.setRecordCount((int) Daos.queryCount(this.dao(), sql));// 记录数需手动设置
sql.setPager(pager);
sql.setCallback(new QueryVoCallback(clazz));
sql.setCallback(Sqls.callback.maps());
this.dao().execute(sql);
List<C> list = sql.getList(clazz);
return new Pagination<>(pageNumber, pageSize, pager.getRecordCount(), list);
List<Map> list = sql.getList(Map.class);
try {
C c = clazz.getDeclaredConstructor().newInstance();
List<C> voList = list.stream().map(map -> BeanUtil.fillBeanWithMapIgnoreCase(map, c, true)).toList();
return new Pagination(pageNumber, pageSize, pager.getRecordCount(), voList);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
@@ -1,3 +1,349 @@
//package com.budwk.app.base.sqlCallback;
//
//import cn.hutool.core.util.ObjectUtil;
//import cn.hutool.core.util.ReflectUtil;
//import cn.hutool.core.util.TypeUtil;
//import com.mysql.cj.MysqlType;
//import org.nutz.dao.DaoException;
//import org.nutz.dao.impl.jdbc.BlobValueAdaptor;
//import org.nutz.dao.jdbc.Jdbcs;
//import org.nutz.dao.pager.ResultSetLooping;
//import org.nutz.dao.sql.Sql;
//import org.nutz.dao.sql.SqlCallback;
//import org.nutz.dao.sql.SqlContext;
//import org.nutz.json.Json;
//
//import java.lang.reflect.Field;
//import java.lang.reflect.Type;
//import java.sql.*;
//import java.util.ArrayList;
//import java.util.Arrays;
//import java.util.List;
//
///**
// * VO 查询回调
// * 如果你的字段是json类型 那么也可以根据VO的类型来做映射
// * todo 暂时只支持List<String>
// * @author zhaoxinyu
// */
//public class QueryVoCallback implements SqlCallback {
//
// protected Class<?> clazz;
//
// public QueryVoCallback(Class<?> clazz) {
// this.clazz = clazz;
// }
//
// @Override
// public Object invoke(Connection connection, ResultSet rs, Sql sql) throws SQLException {
// final ResultSetMetaData meta = rs.getMetaData();
//
//
// ResultSetLooping ing = new ResultSetLooping() {
// protected boolean createObject(int index, ResultSet rs, SqlContext context, int rowCount) {
// try {
// Object obj = clazz.getDeclaredConstructor().newInstance();
// int count = meta.getColumnCount();
//
// for (int i = 1; i <= count; ++i) {
// String columnName = meta.getColumnLabel(i);
// int columnType = meta.getColumnType(i);
// String columnTypeName = meta.getColumnTypeName(i);
//
// // 根据列名找到对应的字段
// Field field = getField(clazz, columnName);
//
// if(ObjectUtil.isNull(field)){
// continue;
// }
//
// Object object = null;
//
// try {
// // 特殊类型处理
// if (columnType == Types.TIMESTAMP || columnType == Types.DATE) {
// object = rs.getTimestamp(i);
// // 如果是Date类型且值为Timestamp,转换为Date
// if (object instanceof Timestamp && field.getType() == Date.class) {
// object = new Date(((Timestamp) object).getTime());
// }
// } else if (columnType == Types.BLOB) {
// object = (new BlobValueAdaptor(Jdbcs.getFilePool())).get(rs, columnName);
// } else if (columnType == Types.CLOB) {
// object = rs.getString(i);
// } else if (columnType == Types.LONGVARCHAR || columnType == Types.LONGNVARCHAR) {
// if ("JSON".equalsIgnoreCase(columnTypeName)) {
// object = parseJsonField(rs, i, field);
// } else {
// object = rs.getObject(i);
// }
// } else if (columnType == Types.ARRAY) {
// object = rs.getArray(i);
// } else {
// object = rs.getObject(i);
// }
//
// // 设置字段值
// if (ObjectUtil.isNotNull(object)) {
// ReflectUtil.setFieldValue(obj, field, object);
// }
//
// } catch (Exception e) {
// // 记录警告日志,但不中断处理
// System.err.println("设置字段值失败: 字段=" + field.getName() + ", 列=" + columnName + ", 错误=" + e.getMessage());
// continue;
// }
//
//// if (columnType == 93 || columnType == 91) {
//// object = rs.getTimestamp(i);
//// } else if (columnType == 2004) {
//// object = (new BlobValueAdaptor(Jdbcs.getFilePool())).get(rs, name);
//// } else if (columnType == 2005) {
//// object = rs.getString(i);
//// } else if (columnType == -1) {
//// if (columnTypeName.equals(MysqlType.JSON.getName())) {
//// try{
//// Class<?> type = field.getType();
////
//// if (type.isAssignableFrom(List.class)) {
//// Type typeArgument = TypeUtil.getTypeArgument(field.getGenericType());
//// if(ObjectUtil.isNotNull(rs.getString(i))){
//// object = Json.fromJsonAsList(Class.forName(typeArgument.getTypeName()), rs.getString(i));
//// }
//// }else if(type.isArray()){
//// if(ObjectUtil.isNotNull(rs.getString(i))){
//// object = Json.fromJsonAsArray(type.getComponentType(), rs.getString(i));
//// }
//// }else{
//// object = Json.fromJson(type, rs.getString(i));
//// }
//// }catch (Exception e){
//// e.printStackTrace();
//// }
//// } else {
//// object = rs.getObject(i);
//// }
//// } else {
//// object = rs.getObject(i);
//// }
//
// //生成clazz类型的实例
// if(ObjectUtil.isNotNull(field)){
// ReflectUtil.setFieldValue(obj, field, object);
// }
// }
// list.add(obj);
// } catch (SQLException e) {
// throw new DaoException(e);
// } catch (Exception e){
// throw new RuntimeException(e);
// }
// return true;
// }
// };
// ing.doLoop(rs, sql.getContext());
// return ing.getList();
// }
//
// /**
// * 解析JSON字段
// */
// private Object parseJsonField(ResultSet rs, int columnIndex, Field field) {
// try {
// String jsonString = rs.getString(columnIndex);
// if (ObjectUtil.isNull(jsonString)) {
// return null;
// }
//
// Class<?> fieldType = field.getType();
//
// // List类型处理
// if (fieldType.isAssignableFrom(List.class)) {
// Type genericType = field.getGenericType();
// Type[] typeArguments = ((java.lang.reflect.ParameterizedType) genericType).getActualTypeArguments();
// if (typeArguments.length > 0) {
// Class<?> elementType = (Class<?>) typeArguments[0];
// return Json.fromJsonAsList(elementType, jsonString);
// }
// }
// // 数组类型处理
// else if (fieldType.isArray()) {
// Class<?> componentType = fieldType.getComponentType();
// return Json.fromJsonAsArray(componentType, jsonString);
// }
// // 普通对象处理
// else {
// return Json.fromJson(fieldType, jsonString);
// }
// } catch (Exception e) {
// System.err.println("JSON解析失败: 字段=" + field.getName() + ", 错误=" + e.getMessage());
// }
// return null;
// }
//
// /**
// * 多种方式获取字段,支持不同命名规范
// */
// public static Field getField(Class<?> clazz, String columnName) {
// if (clazz == null || columnName == null || columnName.isEmpty()) {
// return null;
// }
//
// // 1. 直接匹配(完全相同)
// Field field = getFieldDirectly(clazz, columnName);
// if (field != null) {
// return field;
// }
//
// // 2. 驼峰命名匹配
// String camelName = underlineToCamel(columnName);
// field = getFieldDirectly(clazz, camelName);
// if (field != null) {
// return field;
// }
//
// // 3. 下划线命名匹配
// String underlineName = camelToUnderline(columnName);
// field = getFieldDirectly(clazz, underlineName);
// if (field != null) {
// return field;
// }
//
// // 4. 忽略大小写匹配
// field = getFieldIgnoreCase(clazz, columnName);
// if (field != null) {
// return field;
// }
//
// // 5. 驼峰转下划线后忽略大小写匹配
// field = getFieldIgnoreCase(clazz, underlineName);
// if (field != null) {
// return field;
// }
//
// // 6. 下划线转驼峰后忽略大小写匹配
// field = getFieldIgnoreCase(clazz, camelName);
// if (field != null) {
// return field;
// }
//
// return null;
// }
//
// /**
// * 直接获取字段(完全匹配)
// */
// private static Field getFieldDirectly(Class<?> clazz, String fieldName) {
// try {
// return clazz.getDeclaredField(fieldName);
// } catch (NoSuchFieldException e) {
// // 继续向上查找父类
// Class<?> superclass = clazz.getSuperclass();
// if (superclass != null && superclass != Object.class) {
// return getFieldDirectly(superclass, fieldName);
// }
// return null;
// }
// }
//
// /**
// * 忽略大小写获取字段
// */
// private static Field getFieldIgnoreCase(Class<?> clazz, String fieldName) {
// Field[] fields = getAllFields(clazz);
// for (Field field : fields) {
// if (field.getName().equalsIgnoreCase(fieldName)) {
// return field;
// }
// }
//
// // 再尝试驼峰和下划线转换后忽略大小写
// String camelName = underlineToCamel(fieldName);
// String underlineName = camelToUnderline(fieldName);
//
// for (Field field : fields) {
// if (field.getName().equalsIgnoreCase(camelName) ||
// field.getName().equalsIgnoreCase(underlineName)) {
// return field;
// }
// }
//
// return null;
// }
//
// /**
// * 获取类及其父类的所有字段
// */
// private static Field[] getAllFields(Class<?> clazz) {
// List<Field> fieldList = new ArrayList<>();
// Class<?> currentClass = clazz;
//
// while (currentClass != null && currentClass != Object.class) {
// fieldList.addAll(Arrays.asList(currentClass.getDeclaredFields()));
// currentClass = currentClass.getSuperclass();
// }
//
// return fieldList.toArray(new Field[0]);
// }
//
// /**
// * 下划线转驼峰
// */
// public static String underlineToCamel(String underlineStr) {
// if (underlineStr == null || !underlineStr.contains("_")) {
// return underlineStr;
// }
//
// StringBuilder result = new StringBuilder();
// String[] parts = underlineStr.toLowerCase().split("_");
//
// for (int i = 0; i < parts.length; i++) {
// if (i == 0) {
// result.append(parts[i]);
// } else {
// result.append(capitalize(parts[i]));
// }
// }
//
// return result.toString();
// }
//
// /**
// * 驼峰转下划线
// */
// public static String camelToUnderline(String camelStr) {
// if (camelStr == null || camelStr.isEmpty()) {
// return camelStr;
// }
//
// StringBuilder result = new StringBuilder();
// for (int i = 0; i < camelStr.length(); i++) {
// char c = camelStr.charAt(i);
// if (Character.isUpperCase(c)) {
// if (i > 0) {
// result.append("_");
// }
// result.append(Character.toLowerCase(c));
// } else {
// result.append(c);
// }
// }
// return result.toString();
// }
//
// /**
// * 首字母大写
// */
// private static String capitalize(String str) {
// if (str == null || str.isEmpty()) {
// return str;
// }
// return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
// }
//
//}
package com.budwk.app.base.sqlCallback;
import cn.hutool.core.util.ObjectUtil;
@@ -0,0 +1,184 @@
package com.budwk.app.base.utils;
import java.lang.reflect.Field;
import java.util.*;
/**
* 字段映射工具类
* 支持多种命名规范的字段匹配和转换
*/
public class FieldMappingUtil {
/**
* 多种方式获取字段,支持不同命名规范
* @param clazz 类
* @param columnName 列名或字段名
* @return Field对象,找不到返回null
*/
public static Field getField(Class<?> clazz, String columnName) {
if (clazz == null || columnName == null || columnName.isEmpty()) {
return null;
}
// 1. 直接匹配(完全相同)
Field field = getFieldDirectly(clazz, columnName);
if (field != null) {
return field;
}
// 2. 驼峰命名匹配
String camelName = underlineToCamel(columnName);
if (!camelName.equals(columnName)) {
field = getFieldDirectly(clazz, camelName);
if (field != null) {
return field;
}
}
// 3. 下划线命名匹配
String underlineName = camelToUnderline(columnName);
if (!underlineName.equals(columnName)) {
field = getFieldDirectly(clazz, underlineName);
if (field != null) {
return field;
}
}
// 4. 忽略大小写匹配
field = getFieldIgnoreCase(clazz, columnName);
if (field != null) {
return field;
}
// 5. 驼峰转下划线后忽略大小写匹配
if (!underlineName.equals(columnName)) {
field = getFieldIgnoreCase(clazz, underlineName);
if (field != null) {
return field;
}
}
// 6. 下划线转驼峰后忽略大小写匹配
if (!camelName.equals(columnName)) {
field = getFieldIgnoreCase(clazz, camelName);
if (field != null) {
return field;
}
}
return null;
}
/**
* 直接获取字段(完全匹配)
*/
private static Field getFieldDirectly(Class<?> clazz, String fieldName) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// 继续向上查找父类
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
return getFieldDirectly(superclass, fieldName);
}
return null;
}
}
/**
* 忽略大小写获取字段
*/
private static Field getFieldIgnoreCase(Class<?> clazz, String fieldName) {
Field[] fields = getAllFields(clazz);
for (Field field : fields) {
if (field.getName().equalsIgnoreCase(fieldName)) {
return field;
}
}
// 再尝试驼峰和下划线转换后忽略大小写
String camelName = underlineToCamel(fieldName);
String underlineName = camelToUnderline(fieldName);
for (Field field : fields) {
String fieldNameLower = field.getName().toLowerCase();
if (fieldNameLower.equals(fieldName.toLowerCase()) ||
fieldNameLower.equals(camelName.toLowerCase()) ||
fieldNameLower.equals(underlineName.toLowerCase())) {
return field;
}
}
return null;
}
/**
* 获取类及其父类的所有字段
*/
private static Field[] getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
Class<?> currentClass = clazz;
while (currentClass != null && currentClass != Object.class) {
fieldList.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}
return fieldList.toArray(new Field[0]);
}
/**
* 下划线转驼峰
*/
public static String underlineToCamel(String underlineStr) {
if (underlineStr == null || !underlineStr.contains("_")) {
return underlineStr;
}
StringBuilder result = new StringBuilder();
String[] parts = underlineStr.toLowerCase().split("_");
for (int i = 0; i < parts.length; i++) {
if (i == 0) {
result.append(parts[i]);
} else {
result.append(capitalize(parts[i]));
}
}
return result.toString();
}
/**
* 驼峰转下划线
*/
public static String camelToUnderline(String camelStr) {
if (camelStr == null || camelStr.isEmpty()) {
return camelStr;
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < camelStr.length(); i++) {
char c = camelStr.charAt(i);
if (Character.isUpperCase(c)) {
if (i > 0) {
result.append("_");
}
result.append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
/**
* 首字母大写
*/
private static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
}
@@ -0,0 +1,59 @@
package com.budwk.app.base.utils;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.sys.models.Sys_menu;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 菜单工具类
*/
public class SysMenuUtil {
/**
* 前端菜单数据
*
* @param menus
* @param parentId
* @return
*/
public static List<Sys_menu> createTreeMenus(List<Sys_menu> menus, String parentId) {
List<Sys_menu> filterMenus = menus.stream().filter(v -> StringUtils.defaultString(v.getParentId(), "").equals(StringUtils.defaultString(parentId, ""))).collect(Collectors.toList());
for (Sys_menu menu : filterMenus) {
List<Sys_menu> childMenus = createTreeMenus(menus, menu.getId());
menu.setChildren(childMenus);
}
return new ArrayList<>(filterMenus);
}
/**
* 获取菜单的根菜单
* @param menus
* @param menu
* @return
*/
public static Sys_menu getRootMenu(List<Sys_menu> menus, Sys_menu menu) {
// 如果当前菜单已经是根菜单(parentId为null),直接返回
if (StrUtil.isBlank(menu.getParentId())) {
return menu;
}
// 查找当前菜单的父菜单
Sys_menu parentMenu = menus.stream()
.filter(m -> m.getId().equals(menu.getParentId()))
.findFirst()
.orElse(null);
// 如果找不到父菜单,说明数据有问题,返回当前菜单
if (parentMenu == null) {
return menu;
}
// 递归查找父菜单的根菜单
return getRootMenu(menus, parentMenu);
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.flow.config;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.cfg.Configuration;
import com.budwk.app.flow.engine.core.FlowEngineImpl;
import com.budwk.app.flow.engine.impl.NutzContext;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean
public class FlowConfiguration {
@IocBean
public FlowEngine flowEngine(NutzContext nutzContext) {
FlowEngine engine = new FlowEngineImpl().configure(new Configuration(nutzContext));
return engine;
}
}
@@ -0,0 +1,109 @@
package com.budwk.app.flow.constant;
/**
* 流程常量
*/
public interface FlowConst {
// 业务流程号
String BUSINESS_NO = "BUSINESS_NO";
// 超级管理员ID
String ADMIN_ID = "flow.admin";
// 自动执行ID
String AUTO_ID = "flow.auto";
String PROCESS_NAME_KEY = "name";
String PROCESS_DISPLAY_NAME_KEY = "displayName";
String PROCESS_TYPE = "type";
// 流程定义idkey
String PROCESS_DEFINE_ID_KEY = "processDefineId";
// 流程设计idkey
String PROCESS_DESIGN_ID_KEY = "processDesignId";
// 流程任务id
String PROCESS_TASK_ID_KEY = "processTaskId";
// 流程实例id
String PROCESS_INSTANCE_ID_KEY = "processInstanceId";
// 表单数据前辍
String FORM_DATA_PREFIX = "f_";
// 表单数据
String FORM_DATA = FORM_DATA_PREFIX + "data";
// 任务表单数据前辍
String TASK_FORM_DATA_PREFIX = "tf_";
// 审批意见
String APPROVAL_COMMENT = "tf_approvalComment";
// 审批提交附件
String APPROVAL_ATTACHMENT = "tf_approvalAttachment";
// 下一节点执行人
String NEXT_NODE_OPERATOR = "tf_nextNodeOperator";
// 抄送人
String CC_ACTORS = "tf_ccActors";
// 用户ID
String USER_USER_ID = "u_userId";
// 用户姓名
String USER_REAL_NAME = "u_realName";
// 用户所属部门ID
String USER_DEPT_ID = "u_deptId";
// 用户所属部门名称
String USER_DEPT_NAME = "u_deptName";
// 用户所属岗位id
String USER_POST_ID = "u_postId";
// 用户所属岗位名称
String USER_POST_NAME = "u_postName";
// 提交类型
String SUBMIT_TYPE = "submitType";
// 自动生成的标题
String AUTO_GEN_TITLE = "autoGenTitle";
// 节点名称
String TASK_NAME = "taskName";
// 是否第一个任务节点
String IS_FIRST_TASK_NODE = "isFirstTaskNode";
// 会签变量前辍
String COUNTERSIGN_VARIABLE_PREFIX = "csv_";
// 活跃的会签操作人数
String NR_OF_ACTIVATE_INSTANCES = "nrOfActivateInstances";
// 循环计数器,办理人在列表中的索引
String LOOP_COUNTER = "loopCounter";
// 会签总实例数
String NR_OF_INSTANCES = "nrOfInstances";
// 会签已完成实例数
String NR_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";
// 会签操作人列表
String COUNTERSIGN_OPERATOR_LIST = "operatorList";
// 会签类型 PARALLEL表示并行会签,SEQUENTIAL表示串行会签
String COUNTERSIGN_TYPE = "countersignType";
// 会签不同意标识
String COUNTERSIGN_DISAGREE_FLAG = "countersignDisagreeFlag";
String ACTOR_IDS_KEY = "actorIds";
// 自定义节点默认返回值变量
String CUSTOM_RETURN_VAL = "custom_return_val";
//============================================流程发起人==================================================
// 流程发起人
String INITIATOR = "initiator";
// 流程发起人姓名
String INITIATOR_NAME = "initiatorName";
// 流程发起人工号
String INITIATOR_ACCOUNT = "initiatorAccount";
// 流程发起人部门ID
String INITIATOR_UNIT_ID = "initiatorUnitId";
// 流程发起人部门名称
String INITIATOR_UNIT_NAME = "initiatorUnitName";
// 流程发起人分工会ID
String INITIATOR_UNIT_UNION_ID = "initiatorUnionId";
// 流程发起人分工会名称
String INITIATOR_UNIT_UNION_NAME = "initiatorUnionName";
//============================================流程额外==================================================
// 流程实例名称
String PROCESS_INSTANCE_NAME = "instanceName";
}
@@ -0,0 +1,204 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.vo.ProcessInstanceVO;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@IocBean
@At("/flow/common")
@Api("流程通用")
@Ok("json:full")
public class FlowCommonController {
@Inject
private FlowEngine flowEngine;
@Inject
private Dao dao;
@At("/approval/form")
@SaCheckLogin
@Ok("beetl:/platform/flow/common/approval/form.html")
public void approval(Long taskId, Long instanceId, String businessId) {
}
@At("/instanceInfo")
@SaCheckLogin
@ApiOperation("流程信息")
public Result info(Long instanceId) {
// 流程实例信息
ProcessInstanceVO instanceVO = flowEngine.processInstanceService().findById(instanceId);
// 当前办理环节 可能会有并行网关
List<ProcessTaskVO> todoTaskVos = new ArrayList<>();
List<ProcessTask> todoTasks = flowEngine.processTaskService().getDoingTaskList(instanceId, null);
for (ProcessTask todoTask : todoTasks) {
ProcessTaskVO taskVO = flowEngine.processTaskService().findById(todoTask.getId());
todoTaskVos.add(taskVO);
}
// List<ProcessTaskVO> doneTaskVos = new ArrayList<>();
// List<ProcessTask> doneTaskList = flowEngine.processTaskService().getDoneTaskList(instanceId, null);
// for (ProcessTask doneTask : doneTaskList) {
// ProcessTaskVO taskVO = flowEngine.processTaskService().findById(doneTask.getId());
// doneTaskVos.add(taskVO);
// }
HashMap<String, Object> result = new HashMap<>();
result.put("processInstance", instanceVO);
result.put("todoTasks", todoTaskVos);
// result.put("doneTasks", doneTaskVos);
return Result.success(result);
}
@At
@SaCheckLogin
@ApiOperation("已办任务")
public Result doneTasks(Long instanceId) {
List<ProcessTaskVO> doneTaskVos = new ArrayList<>();
List<ProcessTask> doneTaskList = flowEngine.processTaskService().getDoneTaskList(instanceId, null);
for (ProcessTask doneTask : doneTaskList) {
ProcessTaskVO taskVO = flowEngine.processTaskService().findById(doneTask.getId());
doneTaskVos.add(taskVO);
}
return Result.success().addData(doneTaskVos);
}
@At("/taskInfo")
@SaCheckLogin
@ApiOperation("任务详情")
public Result taskInfo(Long taskId) {
ProcessTaskVO currentTask = flowEngine.processTaskService().findById(taskId);
return Result.success(currentTask);
}
@At("/defineInfo")
@SaCheckLogin
@ApiOperation("流程定义详情")
public Result defineInfo(String defineKey) {
// 通过defineKey获取最新的流程定义
ProcessDefine define = flowEngine.processDefineService().getLastByName(defineKey);
if (define == null) {
return Result.error("未找到指定的流程定义");
}
return Result.success(define);
}
@At("/instanceUrl")
@SaCheckLogin
@ApiOperation("获取流程定义的第一个任务节点")
public Result firstTaskNode(Long processDefineId) {
ProcessModel processModel = flowEngine.processDefineService().getProcessModel(processDefineId);
return Result.success().addData(processModel.getInstanceUrl());
}
@At("/approvalRecord")
@SaCheckLogin
@ApiOperation("获取流程定义的审批记录")
public Result approvalRecord(Long instanceId) {
List<ProcessTask> processTaskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", instanceId)
.and(ProcessTask::getTaskState, "not in", List.of(ProcessTaskStateEnum.DOING, ProcessTaskStateEnum.WITHDRAW, ProcessTaskStateEnum.ABANDON))
.asc(ProcessTask::getUpdatedAt));
List<ProcessTaskVO> res = new ArrayList<>();
processTaskList.forEach(processTask -> {
ProcessTaskVO vo = BeanUtil.toBean(processTask, ProcessTaskVO.class);
res.add(vo);
});
return Result.success(res);
}
@At
@SaCheckLogin
@ApiOperation("启动流程实例并自动完成")
public Result startInstanceAndExecute(@Param("defineKey") String defineKey, @Param("bizData") String bizData, @Param("submitType") Integer submitType) {
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, submitType);
args.set(FlowConst.FORM_DATA, bizData);
ProcessInstance instance = flowEngine.startProcessInstanceByKey(defineKey, null, SecurityUtil.getUserId(), args);
List<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
for (ProcessTask task : doingTaskList) {
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
}
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("启动流程实例并自动完成")
public Result startInstance(@Param("defineKey") String defineKey, @Param("bizData") String bizData, @Param("submitType") Integer submitType) {
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, submitType);
args.set(FlowConst.FORM_DATA, bizData);
flowEngine.startProcessInstanceByKey(defineKey, null, SecurityUtil.getUserId(), args);
return Result.success();
}
@At("/executeTask")
@SaCheckLogin
@ApiOperation("执行任务")
public Result executeTask(@Param("data") String param) {
Dict args = Json.fromJson(Dict.class, param);
Long processTaskId = args.getLong(FlowConst.PROCESS_TASK_ID_KEY);
String operator = SecurityUtil.getUserId();
args.remove(FlowConst.PROCESS_TASK_ID_KEY);
Integer submitType = args.getInt(FlowConst.SUBMIT_TYPE);
if (submitType == null) {
submitType = ProcessSubmitTypeEnum.AGREE.getCode();
}
args.put(FlowConst.SUBMIT_TYPE, submitType);
if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.ROLLBACK.getCode())) {
// 退回上一个节点
flowEngine.executeAndJumpTask(processTaskId, operator, args, null);
} else if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.REJECT.getCode())) {
// 拒绝,跳转到结束节点
flowEngine.executeAndJumpToEnd(processTaskId, operator, args);
} else if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.JUMP.getCode())) {
// 跳转到指定节点
String taskName = args.getStr(FlowConst.TASK_NAME);
flowEngine.executeAndJumpTask(processTaskId, operator, args, taskName);
} else if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.ROLLBACK_TO_OPERATOR.getCode())) {
// 退回发起人
flowEngine.executeAndJumpToFirstTaskNode(processTaskId, operator, args);
} else if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.COUNTERSIGN_DISAGREE.getCode())) {
// 会签不同意,追加不同意标识
args.put(FlowConst.COUNTERSIGN_DISAGREE_FLAG, 1);
flowEngine.executeProcessTask(processTaskId, operator, args);
} else {
// 默认执行
flowEngine.executeProcessTask(processTaskId, operator, args);
}
return Result.success();
}
}
@@ -0,0 +1,103 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.vo.ProcessDefineVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletRequest;
@IocBean
@At("/flow/define")
@Ok("json:full")
@Api("流程定义")
public class FlowDefineController {
@Inject
private Dao dao;
@Inject
private ProcessDefineService processDefineService;
@At("")
@Ok("beetl:/platform/flow/define/index.html")
@SaCheckLogin
public void index() {
}
@At("/preview")
@Ok("beetl:/platform/flow/define/preview.html")
@SaCheckLogin
public void preview(HttpServletRequest request) {
request.setAttribute("id", request.getParameter("id"));
}
@At
@SaCheckLogin
@ApiOperation("删除流程定义")
@Aop(TransAop.READ_COMMITTED)
public Result delete(@Param("id") Long id) {
int count = dao.count(ProcessInstance.class, Cnd.where(ProcessInstance::getProcessDefineId, "=", id));
if (count > 0) {
throw new BaseException("该流程定义正在使用中,请勿删除!");
}
dao.delete(ProcessDefine.class, id);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("流程定义详情")
public Result detail(@Param("id") Long id) {
ProcessDefine define = dao.fetch(ProcessDefine.class, id);
return Result.success(define);
}
@At
@SaCheckLogin
@ApiOperation("获取流程定义分页列表")
public Result pageData(Integer pageNumber, Integer pageSize, String type, String name, String displayName) {
Cnd cnd = Cnd.NEW();
Pagination pagination = processDefineService.listPage(pageNumber, pageSize, cnd);
return Result.success(pagination);
}
@At
@SaCheckLogin
@ApiOperation("启用流程定义")
public Result enable(@Param("id") Long id) {
ProcessDefine define = new ProcessDefine();
define.setId(id);
define.setState(1);
dao.updateIgnoreNull(define);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("禁用流程定义")
public Result disable(@Param("id") Long id) {
ProcessDefine define = new ProcessDefine();
define.setId(id);
define.setState(0);
dao.updateIgnoreNull(define);
return Result.success();
}
}
@@ -0,0 +1,187 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.lang.ClassScanner;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.AssignmentHandler;
import com.budwk.app.flow.entity.ProcessDesign;
import com.budwk.app.flow.service.ProcessDesignService;
import com.budwk.app.sys.services.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@IocBean
@At("/flow/design")
@Ok("json:full")
@Api("流程设计")
@Slf4j
public class FlowDesignController {
@Inject
private SysUserService sysUserService;
@Inject
private ProcessDesignService processDesignService;
@Inject
private Dao dao;
// 获取所有任务参与者处理类
public static final List<JSONObject> ASSIGMENT_HANDLER_LIST;
static {
Set<Class<?>> classes = ClassScanner.scanPackageBySuper("com.budwk.app", AssignmentHandler.class);
ArrayList<JSONObject> list = new ArrayList<>(classes.size());
for (Class<?> aClass : classes) {
try {
AssignmentHandler handler = (AssignmentHandler) ReflectUtil.newInstance(aClass);
JSONObject jsonObject = new JSONObject();
jsonObject.set("value", handler.getClass().getName());
jsonObject.set("order", handler.getOrder());
jsonObject.set("name", handler.getMessage());
list.add(jsonObject);
} catch (Exception e) {
log.error("初始化AssignmentHandler失败: {}", aClass.getName());
}
}
// 排序 按order
list.sort(Comparator.comparingInt(o -> o.getInt("order")));
ASSIGMENT_HANDLER_LIST = Collections.unmodifiableList(list);
}
@At("")
@Ok("beetl:/platform/flow/design/index.html")
@SaCheckLogin
public void index() {
}
@At
@Ok("beetl:/platform/flow/design/designer.html")
@SaCheckLogin
public void designer(HttpServletRequest request) {
request.setAttribute("id", request.getParameter("id"));
}
@At
@SaCheckLogin
@ApiOperation("获取流程设计分页列表")
public Result pageData(Integer pageNumber, Integer pageSize, String name, String key, String category) {
Cnd cnd = Cnd.NEW();
Pagination pagination = processDesignService.listPage(pageNumber, pageSize, cnd);
return Result.success(pagination);
}
@At
@SaCheckLogin
@ApiOperation("保存流程设计")
public Result insert(@Param("design") ProcessDesign processDesign) {
JSONObject jsonObject = new JSONObject();
jsonObject.set("name", processDesign.getName());
jsonObject.set("displayName", processDesign.getDisplayName());
jsonObject.set("category", processDesign.getCategory());
jsonObject.set("instanceUrl", processDesign.getInstanceUrl());
jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl());
processDesign.setContent(jsonObject);
dao.insert(processDesign);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("修改流程设计")
public Result update(@Param("design") ProcessDesign processDesign) {
JSONObject jsonObject = processDesign.getContent();
jsonObject.set("name", processDesign.getName());
jsonObject.set("displayName", processDesign.getDisplayName());
jsonObject.set("category", processDesign.getCategory());
jsonObject.set("instanceUrl", processDesign.getInstanceUrl());
jsonObject.set("h5InstanceUrl", processDesign.getH5InstanceUrl());
processDesign.setContent(jsonObject);
processDesignService.update(processDesign);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("修改流程设计")
public Result updateContent(@Param("design") ProcessDesign processDesign) {
processDesignService.update(processDesign);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("删除流程设计")
public Result delete(@Param("id") Long id) {
dao.delete(ProcessDesign.class, id);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("流程设计详情")
public Result detail(@Param("id") Long id) {
ProcessDesign design = dao.fetch(ProcessDesign.class, id);
return Result.success(design);
}
@At
@SaCheckLogin
@ApiOperation("发布流程设计")
@Aop(TransAop.READ_COMMITTED)
public Result deploy(@Param("id") Long id) {
processDesignService.deploy(id);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("获取流程设计任务参与者处理类")
public Result assigmentHandlerClass() {
return Result.success(ASSIGMENT_HANDLER_LIST);
}
@At
@SaCheckLogin
@ApiOperation("获取流程设计任务参与者分页数据")
public Result assigneePage(Integer pageNumber, Integer pageSize, String keyword, @Param("userIds") String[] userIds) {
Sql sql = Sqls.create("select id,loginname,username,unitName from vw_user $condition");
Cnd cnd = Cnd.NEW();
// cnd.andEX("id", "in", userIds);
Pagination pagination = sysUserService.listPageMap(pageNumber, pageSize, sql);
return Result.success(pagination);
}
@At
@SaCheckLogin
@ApiOperation("获取流程设计任务参与者分页数据回显")
public Result assigneeEcho(@Param("userIds") String[] userIds) {
Sql sql = Sqls.create("select id,loginname,username,unitName from vw_user where id in (@userIds)");
sql.setParam("userIds", userIds);
List<NutMap> list = sysUserService.listMap(sql);
return Result.success(list);
}
}
@@ -0,0 +1,57 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessDesign;
import com.budwk.app.flow.entity.ProcessDesignHis;
import com.budwk.app.flow.service.ProcessDesignService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@At("/flow/designer")
@Ok("json:full")
@Api("流程设计")
public class FlowDesignerController {
@Inject
private ProcessDesignService processDesignService;
@Inject
private Dao dao;
@At
@SaCheckLogin
@ApiOperation("保存流程设计")
public Result insert(ProcessDesign processDesign){
dao.insert(processDesign);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("修改流程设计")
public Result update(ProcessDesign processDesign){
processDesignService.update(processDesign);
return Result.success();
}
@At
@SaCheckLogin
@ApiOperation("删除流程设计")
public Result delete(@Param("id")Long id){
dao.delete(ProcessDesign.class, id);
return Result.success();
}
}
@@ -0,0 +1,34 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.lang.Dict;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.service.ProcessInstanceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@At("/flow/instance")
@Ok("json:full")
@Api("流程实例")
public class FlowInstanceController {
@Inject
private ProcessInstanceService processInstanceService;
@At
@SaCheckLogin
@ApiOperation("启动并执行流程实例")
public Result startAndExecute(Dict args) {
Long processDefineId = args.getLong(FlowConst.PROCESS_DEFINE_ID_KEY);
args.remove(FlowConst.PROCESS_DEFINE_ID_KEY);
processInstanceService.startAndExecute(processDefineId,args);
return Result.success();
}
}
@@ -0,0 +1,176 @@
package com.budwk.app.flow.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@IocBean
@At("/flow/todoCenter")
@Ok("json:full")
public class FlowTodoCenter {
@Inject
private Dao dao;
@Inject
private SysUserService sysUserService;
@At("")
@SaCheckLogin
@Ok("beetl:/platform/flow/todoCenter/index.html")
public void index() {
}
@At
@SaCheckLogin
public Result todo(Integer pageNumber, Integer pageSize, String keyword, String categoty) {
Sql sql = Sqls.create("""
SELECT
t.id,
t.displayName AS taskName,
t.taskState,
t.formKey,
t.createdAt,
t.finishTime,
t.variable,
ins.state AS instanceState
FROM
wf_process_task t
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
cnd.and("ta.actorId", "=", SecurityUtil.getUserId());
cnd.desc("t.createdAt");
sql.setCondition(cnd);
Pagination pagination = sysUserService.listPageMap(pageNumber, pageSize, sql);
List<NutMap> list = pagination.getList();
for (NutMap row : list) {
NutMap variable = Json.fromJson(NutMap.class, row.getString("variable"));
row.put("variable", variable);
}
return Result.success(pagination);
}
@At
@SaCheckLogin
public Result done(Integer pageNumber, Integer pageSize, String keyword, String categoty) {
Sql sql = Sqls.create("""
SELECT
t.id,
t.displayName AS taskName,
t.taskState,
t.formKey,
t.finishTime,
ins.state AS instanceState
FROM
wf_process_task t
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("t.taskState", "=", ProcessTaskStateEnum.FINISHED.getCode());
cnd.and("ta.actorId", "=", SecurityUtil.getUserId());
cnd.desc("t.createdAt");
sql.setCondition(cnd);
Pagination pagination = sysUserService.listPageMap(pageNumber, pageSize, sql);
List<NutMap> list = pagination.getList();
for (NutMap row : list) {
NutMap variable = Json.fromJson(NutMap.class, row.getString("variable"));
row.put("variable", variable);
}
return Result.success(pagination);
}
@At
@SaCheckLogin
public Result started(Integer pageNumber, Integer pageSize, String keyword, String categoty) {
Sql sql = Sqls.create("""
SELECT
ins.id,
ins.processDefineId,
ins.state,
ins.businessNo,
ins.operator,
ins.variable,
ins.createdAt,
GROUP_CONCAT(t.displayName) as taskName
FROM
wf_process_instance ins
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id and t.taskState = 10
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("ins.operator", "=", SecurityUtil.getUserId());
cnd.desc("ins.createdAt");
sql.setCondition(cnd);
Pagination pagination = sysUserService.listPageMap(pageNumber, pageSize, sql);
List<NutMap> list = pagination.getList();
for (NutMap row : list) {
NutMap variable = Json.fromJson(NutMap.class, row.getString("variable"));
row.put("variable", variable);
}
return Result.success(pagination);
}
@At
@SaCheckLogin
public Result statistics() {
// 查询我发起的流程
int startedCount = dao.count(ProcessInstance.class, Cnd.where(ProcessInstance::getOperator, "=", SecurityUtil.getUserId()));
// 查询我的待办任务
Sql todoSql = Sqls.create("""
SELECT
count(1)
FROM
wf_process_task t
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
WHERE
ta.actorId = @userId
AND t.taskState = 10
""");
todoSql.setParam("userId", SecurityUtil.getUserId());
todoSql.setCallback(Sqls.callback.integer());
dao.execute(todoSql);
int todoCount = todoSql.getInt();
// 查询我的已办任务
Sql doneSql = Sqls.create("""
SELECT
count(1)
FROM
wf_process_task t
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
WHERE
ta.actorId = @userId
AND t.taskState = 20
""");
doneSql.setParam("userId", SecurityUtil.getUserId());
doneSql.setCallback(Sqls.callback.integer());
dao.execute(doneSql);
int doneCount = doneSql.getInt();
return Result.success(Map.of("startedCount", startedCount, "todoCount", todoCount, "doneCount", doneCount));
}
}
@@ -0,0 +1,13 @@
package com.budwk.app.flow.engine;
import com.budwk.app.flow.engine.core.Execution;
/**
*
* 模型行为
* @author mldong
* @date 2023/4/25
*/
public interface Action {
public void execute(Execution execution);
}
@@ -0,0 +1,29 @@
package com.budwk.app.flow.engine;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.model.TaskModel;
import java.util.List;
/**
*
* 分配参与者的处理接口
* @author mldong
* @date 2023/6/15
*/
public interface AssignmentHandler {
/**
* 分配参与者方法,可获取到当前的执行对象
* @param model 模型对象
* @param execution 执行对象
* @return Object 参与者对象
*/
List<String> assign(TaskModel model, Execution execution);
default String getMessage() {
return this.getClass().getSimpleName();
}
default int getOrder() {
return Integer.MIN_VALUE;
}
}
@@ -0,0 +1,22 @@
package com.budwk.app.flow.engine;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.entity.Candidate;
import java.util.List;
/**
*
* 候选人处理接口
* @author mldong
* @date 2023/6/26
*/
public interface CandidateHandler {
/**
* 根据任务模型参数获取候选人信息
* @param model
* @return
*/
List<Candidate> handle(TaskModel model);
}
@@ -0,0 +1,54 @@
package com.budwk.app.flow.engine;
import java.util.List;
/**
*
* 服务上下文接口,类似spring的ioc
* @author mldong
* @date 2023/4/26
*/
public interface Context {
/**
* 根据服务名称、实例向服务工厂注册
* @param name 服务名称
* @param object 服务实例
*/
void put(String name, Object object);
/**
* 根据服务名称、类型向服务工厂注册
* @param name 服务名称
* @param clazz 类型
*/
void put(String name, Class<?> clazz);
/**
* 判断是否存在给定的服务名称
* @param name 服务名称
* @return
*/
boolean exist(String name);
/**
* 根据给定的类型查找服务实例
* @param clazz 类型
* @return
*/
<T> T find(Class<T> clazz);
/**
* 根据给定的类型查找所有此类型的服务实例
* @param clazz 类型
* @return
*/
<T> List<T> findList(Class<T> clazz);
/**
* 根据给定的服务名称、类型查找服务实例
* @param name 服务名称
* @param clazz 类型
* @return
*/
<T> T findByName(String name, Class<T> clazz);
}
@@ -0,0 +1,19 @@
package com.budwk.app.flow.engine;
import com.budwk.app.flow.engine.core.Execution;
/**
*
* 决策处理器接口
* @author mldong
* @date 2023/5/1
*/
public interface DecisionHandler {
/**
* 定义决策方法,实现类需要根据执行对象做处理,并返回后置流转的name
* @param execution
* @return String 后置流转的name
*/
String decide(Execution execution);
}
@@ -0,0 +1,125 @@
package com.budwk.app.flow.engine;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.engine.cfg.Configuration;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService;
import java.util.List;
/**
* 流程引擎接口
*
* @author mldong
* @date 2023/5/29
*/
public interface FlowEngine {
/**
* 根据Configuration对象配置实现类
*
* @param config 全局配置对象
* @return FlowEngine 流程引擎
*/
FlowEngine configure(Configuration config);
/**
* 获取流程定义服务
*
* @return ProcessDefineService
*/
ProcessDefineService processDefineService();
/**
* 获取流程实例服务
*
* @return ProcessInstanceService
*/
ProcessInstanceService processInstanceService();
/**
* 获取流程任务服务
*
* @return ProcessTaskService
*/
ProcessTaskService processTaskService();
/**
* 根据流程定义KEY、操作人ID、启动流程参数启动流程实例 (会自动选择KEY相同的流程定义中最新版本的那个)
*
* @param key 流程定义KEY
* @param businessKey 业务标识
* @param operator 操作人ID
* @param args 启动参数
* @return
*/
ProcessInstance startProcessInstanceByKey(String key, String businessKey, String operator, Dict args);
/**
* 根据流程定义ID、操作人ID、启动流程参数启动流程实例
*
* @param id 流程定义ID
* @param businessKey 业务标识
* @param operator 操作人ID
* @param args 启动流程参数
* @return ProcessInstance 流程实例
*/
ProcessInstance startProcessInstanceById(Long id, String businessKey, String operator, Dict args);
/**
* 根据流程定义ID、操作人ID、启动流程参数启动流程实例
*
* @param id 流程定义ID
* @param businessKey 业务标识
* @param operator 操作人ID
* @param args 启动流程参数
* @param parentId
* @param parentNodeName
* @return ProcessInstance 流程实例
*/
ProcessInstance startProcessInstanceById(Long id, String businessKey, String operator, Dict args, Long parentId, String parentNodeName);
/**
* 执行流程任务
*
* @param processTaskId
* @param operator
* @param args
* @return
*/
List<ProcessTask> executeProcessTask(Long processTaskId, String operator, Dict args);
/**
* 执行流程任务并跳转
*
* @param processTaskId
* @param operator
* @param args
* @param nodeName
* @return
*/
List<ProcessTask> executeAndJumpTask(Long processTaskId, String operator, Dict args, String nodeName);
/**
* 执行流程任务并跳转到结束节点
*
* @param processTaskId
* @param operator
* @param args
* @return
*/
List<ProcessTask> executeAndJumpToEnd(Long processTaskId, String operator, Dict args);
/**
* 执行流程任务并跳转到第一个任务节点
*
* @param processTaskId
* @param operator
* @param args
* @return
*/
List<ProcessTask> executeAndJumpToFirstTaskNode(Long processTaskId, String operator, Dict args);
}
@@ -0,0 +1,18 @@
package com.budwk.app.flow.engine;
import com.budwk.app.flow.engine.core.Execution;
/**
*
* 流程节点拦截器
* @author mldong
* @date 2023/9/3
*/
public interface FlowInterceptor{
/**
* 拦截方法,参数为执行对象
* @param execution 执行对象。可从中获取执行的数据
*/
void intercept(Execution execution);
}
@@ -0,0 +1,31 @@
package com.budwk.app.flow.engine.cfg;
import com.budwk.app.flow.engine.Context;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.impl.SimpleContext;
import com.budwk.app.flow.engine.parser.impl.*;
/**
*
* 流程引擎配置类
* @author mldong
* @date 2022/6/12
*/
public class Configuration {
public Configuration() {
this(new SimpleContext());
}
public Configuration(Context context) {
ServiceContext.setContext(context);
ServiceContext.put("decision", DecisionParser.class);
ServiceContext.put("end", EndParser.class);
ServiceContext.put("fork", ForkParser.class);
ServiceContext.put("join", JoinParser.class);
ServiceContext.put("start", StartParser.class);
ServiceContext.put("task", TaskParser.class);
ServiceContext.put("custom",CustomParser.class);
ServiceContext.put("wfSubProcess", WfSubProcessParser.class);
}
}
@@ -0,0 +1,70 @@
package com.budwk.app.flow.engine.core;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* 执行对象参数
* @author mldong
* @date 2023/4/25
*/
@Data
public class Execution {
// 流程实例ID
private Long processInstanceId;
// 当前流程任务ID
private Long processTaskId;
// 执行对象扩展参数
private Dict args;
// 当前流程模型
private ProcessModel processModel;
// 当前任务
private ProcessTask processTask;
// 当前流程实例
private ProcessInstance processInstance;
// 所有任务集合
private List<ProcessTask> processTaskList = new ArrayList<>();
// 是否可合并
private boolean isMerged;
// 流程引擎对象
private FlowEngine engine;
// 操作人
private String operator;
// 当前节点模型
private NodeModel nodeModel;
/**
* 添加任务到任务集合
* @param processTask
*/
public void addTask(ProcessTask processTask) {
this.processTaskList.add(processTask);
}
/**
* 添加任务集合
* @param processTasks
*/
public void addTasks(List<ProcessTask> processTasks) {
this.processTaskList.addAll(processTasks);
}
/**
* 获取正在进行中的任务列表
* @return
*/
public List<ProcessTask> getDoingTaskList() {
return this.processTaskList.stream().filter(item->{
return ProcessTaskStateEnum.DOING.getCode().equals(item.getTaskState());
}).collect(Collectors.toList());
}
}
@@ -0,0 +1,241 @@
package com.budwk.app.flow.engine.core;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.cfg.Configuration;
import com.budwk.app.flow.engine.model.*;
import com.budwk.app.flow.engine.parser.ModelParser;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import java.util.Collections;
import java.util.List;
/**
* 工作流引擎实现
*
* @author mldong
* @date 2023/5/29
*/
public class FlowEngineImpl implements FlowEngine {
protected Configuration configuration;
private ProcessDefineService processDefineService;
private ProcessInstanceService processInstanceService;
private ProcessTaskService processTaskService;
@Override
public FlowEngine configure(Configuration config) {
this.configuration = config;
processDefineService = ServiceContext.find(ProcessDefineService.class);
processInstanceService = ServiceContext.find(ProcessInstanceService.class);
processTaskService = ServiceContext.find(ProcessTaskService.class);
return this;
}
@Override
public ProcessDefineService processDefineService() {
return processDefineService;
}
@Override
public ProcessInstanceService processInstanceService() {
return processInstanceService;
}
@Override
public ProcessTaskService processTaskService() {
return processTaskService;
}
@Override
public ProcessInstance startProcessInstanceByKey(String key, String businessKey, String operator, Dict args) {
ProcessDefine define = processDefineService.getLastByName(key);
return startProcessInstanceById(define.getId(), businessKey, operator, args);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public ProcessInstance startProcessInstanceById(Long id, String businessKey, String operator, Dict args) {
return startProcessInstanceById(id, businessKey, operator, args, null, null);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public ProcessInstance startProcessInstanceById(Long id, String businessKey, String operator, Dict args, Long parentId, String parentNodeName) {
if (StrUtil.isBlank(businessKey)) {
// throw new RuntimeException("业务主键不能为空");
}
if (args == null) args = Dict.create();
// 1. 根据流程定义ID查询流程定义文件
ProcessDefine processDefine = processDefineService.getById(id);
if (processDefine == null) {
throw new BaseException("没有流程定义");
}
// 2. 将流程定义文件转成流程模型
ProcessModel processModel = processDefineService.processDefineToModel(processDefine);
// 3. 根据流程定义对象创建流程实例
ProcessInstance processInstance = processInstanceService.createProcessInstance(processDefine, businessKey, operator, args, parentId, parentNodeName);
args.set(FlowConst.PROCESS_INSTANCE_ID_KEY, processInstance.getId());
// 4. 构建执行参数对象
Execution execution = new Execution();
execution.setProcessModel(processModel);
execution.setProcessInstance(processInstance);
execution.setProcessInstanceId(processInstance.getId());
execution.setEngine(this);
execution.setArgs(args);
// 5. 拿到开始节点模型,调用其execute方法
processModel.getStart().execute(execution);
return processInstance;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public List<ProcessTask> executeProcessTask(Long processTaskId, String operator, Dict args) {
Execution execution = execute(processTaskId, operator, args);
if (execution == null) return Collections.emptyList();
ProcessModel processModel = execution.getProcessModel();
// 7. 根据流程任务名称获取对应的任务节点模型
NodeModel nodeModel = processModel.getNode(execution.getProcessTask().getTaskName());
// 8. 调用节点模型执行方法
nodeModel.execute(execution);
return execution.getProcessTaskList();
}
@Override
@Aop(TransAop.READ_COMMITTED)
public List<ProcessTask> executeAndJumpTask(Long processTaskId, String operator, Dict args, String nodeName) {
Execution execution = execute(processTaskId, operator, args);
if (execution == null) return Collections.emptyList();
ProcessModel model = execution.getProcessModel();
if (StrUtil.isEmpty(nodeName)) {
ProcessTask newTask = processTaskService.rejectTask(model, execution.getProcessTask());
execution.addTask(newTask);
} else {
NodeModel nodeModel = model.getNode(nodeName);
if (nodeModel == null) {
throw new BaseException("根据节点名称[" + nodeName + "]无法找到节点模型");
}
// 判断是否为第一个任务节点
if (nodeModel instanceof TaskModel) {
TaskModel taskModel = (TaskModel) nodeModel;
if (FlowUtil.isFistTaskName(model, taskModel.getName())) {
// 第一个任务节点为申请节点,处理人等于流程发起人
taskModel.setAssignee(execution.getProcessInstance().getOperator());
}
}
//动态创建转移对象,由转移对象执行execution实例
TransitionModel tm = new TransitionModel();
tm.setTarget(nodeModel);
tm.setEnabled(true);
tm.execute(execution);
}
return execution.getProcessTaskList();
}
@Override
@Aop(TransAop.READ_COMMITTED)
public List<ProcessTask> executeAndJumpToEnd(Long processTaskId, String operator, Dict args) {
Execution execution = execute(processTaskId, operator, args);
if (execution == null) return Collections.emptyList();
ProcessModel model = execution.getProcessModel();
List<EndModel> endModelList = model.getModels(EndModel.class);
endModelList.forEach(endModel -> {
TransitionModel tm = new TransitionModel();
tm.setTarget(endModel);
tm.setEnabled(true);
tm.execute(execution);
});
return execution.getProcessTaskList();
}
@Override
public List<ProcessTask> executeAndJumpToFirstTaskNode(Long processTaskId, String operator, Dict args) {
Execution execution = execute(processTaskId, operator, args);
if (execution == null) return Collections.emptyList();
ProcessModel model = execution.getProcessModel();
StartModel startModel = model.getStart();
startModel.getOutputs().forEach(transitionModel -> {
transitionModel.setEnabled(true);
// 调整参与者为流程发起人
if (transitionModel.getTarget() instanceof TaskModel) {
TaskModel taskModel = (TaskModel) transitionModel.getTarget();
taskModel.setAssignee(execution.getProcessInstance().getOperator());
}
transitionModel.execute(execution);
});
return execution.getProcessTaskList();
}
/**
* 生成执行对象
*
* @param processTaskId
* @param operator
* @param args
* @return
*/
private Execution execute(Long processTaskId, String operator, Dict args) {
// 1.1 根据id查询正在进行中的流程任务
ProcessTask processTask = processTaskService.getById(processTaskId);
if (processTask == null || !ProcessTaskStateEnum.DOING.getCode().equals(processTask.getTaskState())) {
throw new BaseException("没有进行中的流程任务");
}
// 1.2 判断是否可以执行任务
if (!processTaskService.isAllowed(processTask, operator)) {
// 当前参与者不能执行该流程任务
throw new BaseException("当前参与者不能执行该流程任务");
}
// 2. 根据流程任务查询流程实例
ProcessInstance processInstance = processInstanceService.getById(processTask.getProcessInstanceId());
// 3. 根据流程实例查询流程定义
ProcessDefine processDefine = processDefineService.getById(processInstance.getProcessDefineId());
// 4. 将流程定义文件转成流程模型
ProcessModel processModel = ModelParser.parse(JSONUtil.toJsonStr(processDefine.getContent()));
// 5. 将流程任务状态修改为已完成
processTaskService.finishProcessTask(processTaskId, operator, args);
processTask.setTaskState(ProcessTaskStateEnum.FINISHED.getCode());
// 6. 根据流程定义、实例、任务构建执行参数对象
Execution execution = new Execution();
execution.setProcessModel(processModel);
execution.setProcessInstance(processInstance);
execution.setProcessInstanceId(processInstance.getId());
execution.setProcessTask(processTask);
execution.setProcessTaskId(processTaskId);
execution.setOperator(operator);
execution.setEngine(this);
Dict processInstanceVariable = JSONUtil.toBean(processInstance.getVariable(), Dict.class);
Dict newArgs = Dict.create();
newArgs.putAll(processInstanceVariable);
newArgs.putAll(args);
execution.setArgs(newArgs);
// 如果提交参数中存在f_前辍参数,则更新到流程实例变量中
Dict addArgs = Dict.create();
args.forEach((key, value) -> {
if (key.startsWith(FlowConst.FORM_DATA_PREFIX)) {
addArgs.put(key, value);
}
});
if (ObjectUtil.isNotEmpty(addArgs)) {
processInstanceService.addVariable(processInstance.getId(), addArgs);
}
return execution;
}
}
@@ -0,0 +1,51 @@
package com.budwk.app.flow.engine.core;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import com.budwk.app.flow.engine.Context;
import java.util.List;
/**
*
* 单例服务上下文
* @author mldong
* @date 2022/6/12
*/
public class ServiceContext {
private static Context context;
public static void setContext(Context context) {
ServiceContext.context = context;
}
public static void put(String name, Object object) {
Assert.notNull(context,"未注册服务上下文");
context.put(name, object);
}
public static void put(String name, Class<?> clazz) {
Assert.notNull(context,"未注册服务上下文");
context.put(name, ReflectUtil.newInstance(clazz));
}
public static boolean exist(String name) {
Assert.notNull(context,"未注册服务上下文");
return context.exist(name);
}
public static <T> T find(Class<T> clazz) {
Assert.notNull(context,"未注册服务上下文");
return context.find(clazz);
}
public static <T> List<T> findList(Class<T> clazz) {
Assert.notNull(context,"未注册服务上下文");
return context.findList(clazz);
}
public static <T> T findByName(String name, Class<T> clazz) {
Assert.notNull(context,"未注册服务上下文");
return context.findByName(name, clazz);
}
}
@@ -0,0 +1,19 @@
package com.budwk.app.flow.engine.event;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import lombok.Builder;
import lombok.Data;
/**
* 流程开始事件
* @author mldong
* @date 2023/12/5
*/
@Data
@Builder
public class ProcessEvent {
// 流程事件类型
private ProcessEventTypeEnum eventType;
// 执行源id(流程实例id,流程任务id)
private Long sourceId;
}
@@ -0,0 +1,10 @@
package com.budwk.app.flow.engine.event;
/**
* 流程事件监听接口
* @author mldong
* @date 2023/12/5
*/
public interface ProcessEventListener {
void onEvent(ProcessEvent event);
}
@@ -0,0 +1,21 @@
package com.budwk.app.flow.engine.event;
import com.budwk.app.flow.engine.core.ServiceContext;
import java.util.List;
/**
* 流程事件发布者类
* @author mldong
* @date 2023/12/5
*/
public class ProcessPublisher {
// 事件通知方法
public static void notify(ProcessEvent event) {
// 这里直接从上下文中获取所有的监听器,并调用监听器的onEvent方法==>通知
List<ProcessEventListener> processEventListenerList = ServiceContext.findList(ProcessEventListener.class);
processEventListenerList.forEach(processEventListener -> {
processEventListener.onEvent(event);
});
}
}
@@ -0,0 +1,17 @@
package com.budwk.app.flow.engine.handlers;
import com.budwk.app.flow.engine.core.Execution;
/**
* 流程各模型操作处理接口
* @author mldong
* @date 2023/5/17
*/
public interface IHandler {
/**
* 子类需要实现的方法,来处理具体的操作
* @param execution 执行对象
*/
void handle(Execution execution);
}
@@ -0,0 +1,98 @@
package com.budwk.app.flow.engine.handlers.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.expression.ExpressionUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.IHandler;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.enums.CountersignTypeEnum;
import java.util.List;
import java.util.stream.Collectors;
/**
* 会签任务处理,用于判断会签任务是否可通过
* @author mldong
* @date 2023/11/24
*/
public class CountersignHandler implements IHandler {
private TaskModel taskModel;
public CountersignHandler(TaskModel taskModel) {
this.taskModel = taskModel;
}
@Override
public void handle(Execution execution) {
boolean isMerged = false;
String countersignType = taskModel.getExt().get("countersignType","PARALLEL");
String countersignCompletionCondition = taskModel.getExt().get("countersignCompletionCondition","");
String prefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX +taskModel.getName()+"_";
// 会签办理人列表
List<String> operatorList = Convert.toList(String.class,execution.getArgs().get(prefix+FlowConst.COUNTERSIGN_OPERATOR_LIST));
// 循环计数器,办理人在列表中的索引
int loopCounter = operatorList.indexOf(execution.getOperator());
// 追加计数器
execution.getArgs().put(prefix+FlowConst.LOOP_COUNTER,loopCounter);
// 追加已完成数量
execution.getArgs().put(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES,
execution.getArgs().get(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES,0)+1);
/**
* ● 全部通过:为空
* ● 按数量通过:#nrOfCompletedInstances==n,这里表示n人完成任务,会签结束。
* ● 按比例通过:#nrOfCompletedInstances/nrOfInstances==n,这里表示已完成会签数与总实例数达到一定比例时,会签结束
* ● 一票通过:#nrOfCompletedInstances==1,这里表示1人完成任务,会签结束。
* ● 一票否决:ONE_VOTE_VETO
*/
if("ONE_VOTE_VETO".equalsIgnoreCase(countersignCompletionCondition)) {
// 一票否决
if(execution.getArgs().containsKey(FlowConst.COUNTERSIGN_DISAGREE_FLAG)) {
// 存在拒绝标识,则直接通过
isMerged = true;
}
} else if(!StrUtil.isBlank(countersignCompletionCondition)) {
// 根据条件判断是否通过
Dict dict = Dict.create();
execution.getArgs().forEach((k,v)->{
dict.set(k.replace(prefix,""),v);
});
isMerged = Convert.toBool(ExpressionUtil.eval(countersignCompletionCondition, dict));
}
if(!isMerged && CountersignTypeEnum.SEQUENTIAL.toString().equalsIgnoreCase(countersignType)) {
// 串行未通过,则判断是否为最后一个
if (loopCounter == operatorList.size() - 1) {
isMerged = true;
} else {
// 非最后一个,则继续创建会签任务
execution.getEngine().processTaskService().createCountersignTask(taskModel, execution);
}
}
if(!isMerged && CountersignTypeEnum.PARALLEL.toString().equalsIgnoreCase(countersignType)) {
// 是否所有会签任务已完成
isMerged = execution.getEngine().processTaskService().getDoingTaskList(execution.getProcessInstanceId(), new String[]{taskModel.getName()}).size()==0;
if(!isMerged) {
// 未通过,更新已完成实例数量
Dict addVariable = Dict.create();
addVariable.put(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES, execution.getArgs().get(prefix+FlowConst.NR_OF_COMPLETED_INSTANCES));
execution.getEngine().processInstanceService().addVariable(execution.getProcessInstanceId(), addVariable);
}
}
if(isMerged) {
// 获取所有会签参数键值
List<String> keys = execution.getArgs().keySet().stream().filter(k->k.startsWith(prefix)).collect(Collectors.toList());
keys.add(FlowConst.COUNTERSIGN_DISAGREE_FLAG);
// 如果可以合并,则把流程实例中的会签参数清空
execution.getEngine().processInstanceService().removeVariable(
execution.getProcessInstanceId(),
keys.toArray(new String[]{}));
// 如果为并行会签,需将其他会签任务设置为废弃
if(CountersignTypeEnum.PARALLEL.toString().equalsIgnoreCase(countersignType)) {
execution.getEngine().processTaskService().getDoingTaskList(execution.getProcessInstanceId(), new String[]{taskModel.getName()}).stream().forEach(t->{
execution.getEngine().processTaskService().abandonProcessTask(t.getId(), FlowConst.AUTO_ID, execution.getArgs());
});
}
}
execution.setMerged(isMerged);
}
}
@@ -0,0 +1,48 @@
package com.budwk.app.flow.engine.handlers.impl;
import com.budwk.app.flow.engine.FlowInterceptor;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.engine.handlers.IHandler;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum;
import java.util.List;
/**
* 创建任务处理器
* @author mldong
* @date 2023/5/16
*/
public class CreateTaskHandler implements IHandler {
private TaskModel taskModel;
public CreateTaskHandler(TaskModel taskModel) {
this.taskModel = taskModel;
}
@Override
public void handle(Execution execution) {
List<ProcessTask> processTaskList;
if(ProcessTaskPerformTypeEnum.COUNTERSIGN.equals(taskModel.getPerformType())) {
// 会签类型,创建会签任务
processTaskList = execution.getEngine().processTaskService().createCountersignTask(taskModel, execution);
} else {
// 创建普通任务
processTaskList = execution.getEngine().processTaskService().createTask(taskModel, execution);
}
// 将任务添加到执行对象中
execution.addTasks(processTaskList);
// 从服务上下文中获取拦截器执行
ServiceContext.findList(FlowInterceptor.class).forEach(flowInterceptor -> {
flowInterceptor.intercept(execution);
});
// 发布流程任务开始事件
processTaskList.forEach(processTask -> {
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_START).sourceId(processTask.getId()).build());
});
}
}
@@ -0,0 +1,55 @@
package com.budwk.app.flow.engine.handlers.impl;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.engine.handlers.IHandler;
import com.budwk.app.flow.engine.model.EndModel;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.SubProcessModel;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
/**
*
* 结束流程实例的处理器
* @author mldong
* @date 2023/5/29
*/
public class EndProcessHandler implements IHandler {
private EndModel endModel;
public EndProcessHandler(EndModel endModel) {
this.endModel = endModel;
}
@Override
public void handle(Execution execution) {
Integer submitType = execution.getArgs().get(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.AGREE.getCode());
if(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.REJECT.getCode())) {
execution.getEngine().processInstanceService().rejectProcessInstance(execution.getProcessInstanceId());
} else {
execution.getEngine().processInstanceService().finishProcessInstance(execution.getProcessInstanceId());
}
// 发布流程实例结束事件
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_INSTANCE_END).sourceId(execution.getProcessInstanceId()).build());
ProcessInstance processInstance = execution.getProcessInstance();
if(ObjectUtil.isNotNull(processInstance.getParentId())) {
// 如果子流程存在父流程实例,则执行父流程的子流程节点模型方法
ProcessInstance parentInstance = execution.getEngine().processInstanceService().getById(processInstance.getParentId());
if(parentInstance == null) return;
ProcessModel pm = execution.getEngine().processDefineService().getProcessModel(parentInstance.getProcessDefineId());
if(pm == null) return;
SubProcessModel spm = (SubProcessModel)pm.getNode(processInstance.getParentNodeName());
Execution newExecution = new Execution();
newExecution.setEngine(execution.getEngine());
newExecution.setProcessModel(pm);
newExecution.setProcessInstance(parentInstance);
newExecution.setProcessInstanceId(parentInstance.getId());
newExecution.setArgs(execution.getArgs());
spm.execute(newExecution);
execution.addTasks(newExecution.getProcessTaskList());
}
}
}
@@ -0,0 +1,72 @@
package com.budwk.app.flow.engine.handlers.impl;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.handlers.IHandler;
import com.budwk.app.flow.engine.model.*;
import com.budwk.app.flow.service.ProcessTaskService;
import java.util.List;
/**
* 合并分支操作的处理器
* @author mldong
* @date 2023/5/21
*/
public class MergeBranchHandler implements IHandler {
private JoinModel joinModel;
public MergeBranchHandler(JoinModel joinModel) {
this.joinModel = joinModel;
}
@Override
public void handle(Execution execution) {
// 判断是否存在正在执行的任务,存在则不允许合并
execution.setMerged(
execution.getEngine()
.processTaskService()
.getDoingTaskList(execution.getProcessInstanceId(),findActiveNodes()).isEmpty());
}
/**
* 对join节点的所有输入变迁进行递归,查找join至fork节点的所有中间task元素
* @param node
* @param buffer
*/
public static void findForkTaskNames(NodeModel node, StringBuilder buffer) {
if(node instanceof ForkModel) return;
List<TransitionModel> inputs = node.getInputs();
for(TransitionModel tm : inputs) {
if(tm.getSource() instanceof TaskModel) {
buffer.append(tm.getSource().getName()).append(",");
}
findForkTaskNames(tm.getSource(), buffer);
}
}
/**
* 对join节点的所有输入变迁进行递归,查找join至fork节点的所有中间task元素
* @see MergeBranchHandler#findActiveNodes()
*/
public String[] findActiveNodes() {
StringBuilder buffer = new StringBuilder(20);
findForkTaskNames(joinModel, buffer);
String[] taskNames = buffer.toString().split(",");
return taskNames;
}
/**
* 判断流程是否可合并
* @param processInstanceId
* @param nodeModel
* @return
*/
public static boolean isMerged(Long processInstanceId, NodeModel nodeModel) {
// 合并节点
StringBuilder buffer = new StringBuilder(20);
MergeBranchHandler.findForkTaskNames(nodeModel, buffer);
String[] taskNames = buffer.toString().split(",");
ProcessTaskService processTaskService = ServiceContext.find(ProcessTaskService.class);
boolean isMerged = processTaskService.getDoingTaskList(processInstanceId,taskNames).isEmpty();
return isMerged;
}
}
@@ -0,0 +1,38 @@
package com.budwk.app.flow.engine.handlers.impl;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.IHandler;
import com.budwk.app.flow.engine.model.SubProcessModel;
import com.budwk.app.flow.entity.ProcessDefine;
/**
* 启动子流程处理类
*
* @author mldong
* @date 2023/12/7
*/
public class StartSubProcessHandler implements IHandler {
private SubProcessModel model;
public StartSubProcessHandler(SubProcessModel model) {
this.model = model;
}
@Override
public void handle(Execution execution) {
ProcessDefine processDefine = execution.getEngine().processDefineService().getProcessDefineByVersion(model.getName(), model.getVersion());
if (processDefine == null) {
throw new BaseException("子流程" + model.getName() + "定义不存在");
}
Long parentId = execution.getProcessInstanceId();
String parentNodeName = model.getName();
execution.getEngine().startProcessInstanceById(
processDefine.getId(),
null,
execution.getOperator(),
execution.getArgs(),
parentId, parentNodeName);
}
}
@@ -0,0 +1,66 @@
package com.budwk.app.flow.engine.impl;
import com.budwk.app.flow.engine.Context;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
@IocBean
@Slf4j
public class NutzContext implements Context {
@Inject("refer:$ioc")
private Ioc ioc;
@Override
public void put(String name, Object object) {
ioc.addBean(name, object);
}
@Override
public void put(String name, Class<?> clazz) {
try {
Object o = clazz.getDeclaredConstructor().newInstance();
ioc.addBean(name, o);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean exist(String name) {
return ioc.has(name);
}
@Override
public <T> T find(Class<T> clazz) {
try {
T bean = ioc.getByType(clazz);
return bean;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
@Override
public <T> List<T> findList(Class<T> clazz) {
List<T> res = new ArrayList<>();
String[] names = ioc.getNamesByType(clazz);
for (String name : names) {
res.add(ioc.get(clazz, name));
}
return res;
}
@Override
public <T> T findByName(String name, Class<T> clazz) {
return ioc.get(clazz, name);
}
}
@@ -0,0 +1,65 @@
package com.budwk.app.flow.engine.impl;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.budwk.app.flow.engine.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
* 简单的上下文发现实现类
* @author mldong
* @date 2023/4/26
*/
public class SimpleContext implements Context {
private Dict dict = Dict.create();
@Override
public void put(String name, Object object) {
dict.put(name, object);
}
@Override
public void put(String name, Class<?> clazz) {
dict.put(name, ReflectUtil.newInstance(clazz));
}
@Override
public boolean exist(String name) {
return ObjectUtil.isNotNull(dict.getObj(name));
}
@Override
public <T> T find(Class<T> clazz) {
for (Map.Entry<String, Object> entry : dict.entrySet()) {
if (clazz.isInstance(entry.getValue())) {
return clazz.cast(entry.getValue());
}
}
return null;
}
@Override
public <T> List<T> findList(Class<T> clazz) {
List<T> res = new ArrayList<>();
for (Map.Entry<String, Object> entry : dict.entrySet()) {
if (clazz.isInstance(entry.getValue())) {
res.add(clazz.cast(entry.getValue()));
}
}
return res;
}
@Override
public <T> T findByName(String name, Class<T> clazz) {
for (Map.Entry<String, Object> entry : dict.entrySet()) {
if (entry.getKey().equals(name) && clazz.isInstance(entry.getValue())) {
return clazz.cast(entry.getValue());
}
}
return null;
}
}
@@ -0,0 +1,71 @@
//package com.budwk.app.flow.engine.impl;
//
//import cn.hutool.core.util.ObjectUtil;
//import com.budwk.app.flow.engine.Context;
//import org.springframework.beans.factory.config.BeanDefinition;
//import org.springframework.beans.factory.support.DefaultListableBeanFactory;
//import org.springframework.beans.factory.support.RootBeanDefinition;
//import org.springframework.context.ApplicationContext;
//import org.springframework.context.ApplicationContextAware;
//import org.springframework.context.ConfigurableApplicationContext;
//import org.springframework.stereotype.Component;
//
//import java.util.ArrayList;
//import java.util.List;
//import java.util.Map;
//
///**
// *
// * spring的服务查找实现
// * @author mldong
// * @date 2023/5/31
// */
//@Component
//public class SpringContext implements Context {
//// private ApplicationContext applicationContext;
//// DefaultListableBeanFactory beanFactory;
//
// @Override
// public void put(String name, Object object) {
// ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;
// context.getBeanFactory().registerSingleton(name, object);
// }
//
// @Override
// public void put(String name, Class<?> clazz) {
// BeanDefinition definition = new RootBeanDefinition(clazz);
// if(beanFactory==null) {
// beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
// }
// beanFactory.registerBeanDefinition(name, definition);
// }
//
// @Override
// public boolean exist(String name) {
// try{
// return ObjectUtil.isNotNull(SpringUtil.getBean(name));
// } catch (Exception e) {
// return false;
// }
// }
//
// @Override
// public <T> T find(Class<T> clazz) {
// return SpringUtil.getBean(clazz);
// }
//
// @Override
// public <T> List<T> findList(Class<T> clazz) {
// List<T> res = new ArrayList<>();
// Map<String,T> map = SpringUtil.getBeansOfType(clazz);
// map.forEach((k,v)->{
// res.add(v);
// });
// return res;
// }
//
// @Override
// public <T> T findByName(String name, Class<T> clazz) {
// return SpringUtil.getBean(name, clazz);
// }
//}
@@ -0,0 +1,41 @@
package com.budwk.app.flow.engine.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.flow.engine.FlowInterceptor;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.service.ProcessSurrogateService;
import com.budwk.app.flow.service.ProcessTaskService;
import lombok.RequiredArgsConstructor;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 任务拦截器,处理代理人
*
* @author mldong
* @date 2023/12/6
*/
@IocBean
public class SurrogateInterceptor implements FlowInterceptor {
@Inject
private ProcessSurrogateService processSurrogateService;
@Inject
private ProcessTaskService processTaskService;
@Override
public void intercept(Execution execution) {
execution.getProcessTaskList().forEach(processTask -> {
List<String> actorList = processTaskService.getTaskActors(processTask.getId());
actorList.forEach(actor -> {
String agent = processSurrogateService.getSurrogate(actor, execution.getProcessModel().getName());
if (StrUtil.isNotEmpty(agent)) {
processTaskService.addTaskActor(processTask.getId(), CollectionUtil.newArrayList(agent));
}
});
});
}
}
@@ -0,0 +1,24 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.IHandler;
import lombok.Data;
/**
*
* 模型基类
* @author mldong
* @date 2023/4/25
*/
@Data
public class BaseModel {
private String name; // 唯一编码
private String displayName; // 显示名称
/**
* 将执行对象execution交给具体的处理器处理
* @param handler
* @param execution
*/
protected void fire(IHandler handler, Execution execution) {
handler.handle(execution);
}
}
@@ -0,0 +1,76 @@
package com.budwk.app.flow.engine.model;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.IHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
/**
* 自定义节点模型
* @author mldong
* @date 2023/12/7
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CustomModel extends NodeModel{
private String clazz; // 类路径
private String methodName; // 方法名
private String args; // 入参
private String var; // 执行返回值的变量
/**
* 加载模型时初始化的对象实例
*/
private Object invokeObject;
public void exec(Execution execution) {
if(invokeObject == null) {
invokeObject = ReflectUtil.newInstance(clazz);
}
if(invokeObject == null) {
throw new BaseException("自定义模型[class=" + clazz + "]实例化对象失败");
}
if(invokeObject instanceof IHandler) {
IHandler handler = (IHandler)invokeObject;
handler.handle(execution);
} else {
Object[] objects = getArgs(execution.getArgs(), args);
Class<?> paramTypes[] = Arrays.stream(objects).map(Object::getClass).toArray(Class[]::new);
Method method = ReflectUtil.getMethod(invokeObject.getClass(),methodName, paramTypes);
if(method == null) {
throw new BaseException("自定义模型[class=" + clazz + "]无法找到方法名称:" + methodName);
}
Object returnValue = ReflectUtil.invoke(invokeObject, method, objects);
if(StrUtil.isNotEmpty(var)) {
execution.getArgs().put(var, returnValue);
}
}
execution.getEngine().processTaskService().history(execution, this);
runOutTransition(execution);
}
/**
* 根据传递的执行参数、模型的参数列表返回实际的参数对象数组
* @param execArgs 运行时传递的参数数据
* @param args 自定义节点需要的参数
* @return 调用自定义节点类方法的参数数组
*/
private Object[] getArgs(Map<String, Object> execArgs, String args) {
Object[] objects = null;
if(StrUtil.isNotEmpty(args)) {
String[] argArray = args.split(",");
objects = new Object[argArray.length];
for(int i = 0; i < argArray.length; i++) {
objects[i] = execArgs.get(argArray[i]);
}
}
return objects;
}
}
@@ -0,0 +1,55 @@
package com.budwk.app.flow.engine.model;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.expression.ExpressionUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.flow.engine.DecisionHandler;
import com.budwk.app.flow.engine.core.Execution;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 决策模型
*
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class DecisionModel extends NodeModel {
private String expr; // 决策表达式
private String handleClass; // 决策处理类
@Override
public void exec(Execution execution) {
// 执行决策节点自定义执行逻辑
boolean isFound = false;
String nextNodeName = null;
if (StrUtil.isNotEmpty(expr)) {
Object obj = ExpressionUtil.eval(expr, execution.getArgs());
nextNodeName = Convert.toStr(obj, "");
} else if (StrUtil.isNotEmpty(handleClass)) {
DecisionHandler decisionHandler = ReflectUtil.newInstance(handleClass);
nextNodeName = decisionHandler.decide(execution);
}
for (TransitionModel transitionModel : getOutputs()) {
if (StrUtil.isNotEmpty(transitionModel.getExpr()) && Convert.toBool(ExpressionUtil.eval(transitionModel.getExpr(), execution.getArgs()), false)) {
// 决策节点输出边存在表达式,则使用输出边的表达式,true则执行
isFound = true;
transitionModel.setEnabled(true);
transitionModel.execute(execution);
} else if (transitionModel.getTo().equalsIgnoreCase(nextNodeName)) {
// 找到对应的下一个节点
isFound = true;
transitionModel.setEnabled(true);
transitionModel.execute(execution);
}
}
if (!isFound) {
// 找不到下一个可执行路线
throw new BaseException("decision节点无法确定下一步执行路线");
}
}
}
@@ -0,0 +1,23 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.impl.EndProcessHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
* 结束模型
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class EndModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行结束节点自定义执行逻辑
System.out.println(super.toString());
fire(new EndProcessHandler(this), execution);
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.core.Execution;
/**
*
* 分支模型
* @author mldong
* @date 2023/4/25
*/
public class ForkModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行分支节点自定义执行逻辑
runOutTransition(execution);
}
}
@@ -0,0 +1,25 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.impl.MergeBranchHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
* 合并模型
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class JoinModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行合并节点自定义执行逻辑
fire(new MergeBranchHandler(this),execution);
if(execution.isMerged()) {
runOutTransition(execution);
}
}
}
@@ -0,0 +1,152 @@
package com.budwk.app.flow.engine.model;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.flow.engine.Action;
import com.budwk.app.flow.engine.*;
import com.budwk.app.flow.engine.core.Execution;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* 节点模型
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true, exclude = {"inputs", "outputs"})
@Data
public abstract class NodeModel extends BaseModel implements Action {
private String layout;// 布局属性(x,y,w,h)
// 输入边集合
@JsonIgnore
private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
// 输出边集合
@JsonIgnore
private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
private String preInterceptors; // 节点前置拦截器
private String postInterceptors; // 节点后置拦截器
/**
* 由子类自定义执行方法
* @param execution
*/
abstract void exec(Execution execution);
@Override
public void execute(Execution execution) {
// 0.设置当前节点模型
execution.setNodeModel(this);
// 1. 调用前置拦截器
execPreInterceptors(execution);
// 2. 调用子类的exec方法
exec(execution);
// 3. 调用后置拦截器
execPostInterceptors(execution);
}
/**
* 执行输出边
*/
protected void runOutTransition(Execution execution) {
outputs.forEach(tr->{
tr.setEnabled(true);
tr.execute(execution);
});
}
/**
* 执行节点前置拦截器
* @param execution
*/
private void execPreInterceptors(Execution execution) {
if(StrUtil.isEmpty(preInterceptors)) {
preInterceptors = execution.getProcessModel().getPreInterceptors();
}
execInterceptors(preInterceptors,execution);
}
/**
* 执行节点后置拦截器
* @param execution
*/
private void execPostInterceptors(Execution execution) {
if(StrUtil.isEmpty(postInterceptors)) {
postInterceptors = execution.getProcessModel().getPostInterceptors();
}
execInterceptors(postInterceptors,execution);
}
/**
* 执行节点拦截器
* @param execution
*/
private void execInterceptors(String interceptors,Execution execution) {
if(StrUtil.isEmpty(interceptors)) return;
// 存在多个,英文逗号分割
String [] interceptorArr = interceptors.split(",");
for (int i = 0; i < interceptorArr.length; i++) {
String interceptor = interceptorArr[i];
// 反射实例化
FlowInterceptor flowInterceptor = ReflectUtil.newInstance(interceptor);
if(flowInterceptor!=null){
// 调用拦截器方法
flowInterceptor.intercept(execution);
}
}
}
public <T> List<T> getNextModels(Class<T> clazz) {
List<T> models = new ArrayList<T>();
// 记录已递归项,防止死循环
Map<String,Object> temp = new HashMap();
for(TransitionModel tm : this.getOutputs()) {
addNextModels(models, tm, clazz, temp);
}
return models;
}
protected <T> void addNextModels(List<T> models, TransitionModel tm, Class<T> clazz,Map<String,Object> temp) {
if(temp.get(tm.getTo())!=null) {
return;
}
if(clazz.isInstance(tm.getTarget())) {
models.add((T)tm.getTarget());
} else {
for(TransitionModel tm2 : tm.getTarget().getOutputs()) {
temp.put(tm.getTo(), tm.getTarget());
addNextModels(models, tm2, clazz,temp);
}
}
}
/**
* 根据父节点模型、当前节点模型判断是否可退回。可退回条件:
* 1、满足中间无fork、join、subprocess模型
* 2、满足父节点模型如果为任务模型时,参与类型为any
* @param parent 父节点模型
* @return 是否可以退回
*/
public static boolean canRejected(NodeModel current, NodeModel parent) {
boolean result = false;
for(TransitionModel tm : current.getInputs()) {
NodeModel source = tm.getSource();
if(source == parent) {
return true;
}
if(source instanceof ForkModel
|| source instanceof JoinModel
//|| source instanceof SubProcessModel
|| source instanceof StartModel) {
continue;
}
result = result || canRejected(source, parent);
}
return result;
}
@Override
public String toString() {
return StrUtil.format("调用模型节点执行方法:model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName());
}
}
@@ -0,0 +1,156 @@
package com.budwk.app.flow.engine.model;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.flow.engine.CandidateHandler;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.entity.Candidate;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* 流程模型
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ProcessModel extends BaseModel {
private String type; // 流程定义分类
private String category; // 流程定义分类
private String instanceUrl; // 启动实例要填写的表单key
private String h5InstanceUrl; // 启动实例要填写的手机端表单key
private String expireTime; // 期待完成时间变量key
private String instanceNoClass; // 实例编号生成器实现类
private String preInterceptors; // 节点前置拦截器
private String postInterceptors; // 节点后置拦截器
// 流程定义的所有节点
private List<NodeModel> nodes = new ArrayList<NodeModel>();
// 流程定义的所有任务节点
private List<TaskModel> tasks = new ArrayList<TaskModel>();
/**
* 获取开始节点
* @return
*/
public StartModel getStart() {
StartModel startModel = null;
for (int i = 0; i < nodes.size(); i++) {
NodeModel nodeModel = nodes.get(i);
if(nodeModel instanceof StartModel) {
startModel = (StartModel) nodeModel;
break;
}
}
return startModel;
}
/**
* 获取process定义的指定节点名称的节点模型
* @param nodeName 节点名称
* @return
*/
public NodeModel getNode(String nodeName) {
for(NodeModel node : nodes) {
if(node.getName().equals(nodeName)) {
return node;
}
}
return null;
}
/**
* 获取下一个任务节点模型集合
* @param nodeName
* @return
*/
public List<TaskModel> getNextTaskModels(String nodeName) {
List<TaskModel> res = new ArrayList<>();
NodeModel nodeModel = getNode(nodeName);
if(nodeModel == null) return res;
// 获取所有输出边的目标节点
List<NodeModel> nextNodeModelList = nodeModel.getOutputs().stream().map(item->{
return item.getTarget();
}).collect(Collectors.toList());
nextNodeModelList.forEach(item->{
if(item instanceof TaskModel) {
res.add((TaskModel) item);
}
});
if(res.isEmpty()) {
// 如果下一个节点不存在任务节点递归往下找
nextNodeModelList.forEach(item->{
List<TaskModel> taskModelList = getNextTaskModels(item.getName());
res.addAll(taskModelList);
});
}
return res;
}
/**
* 获取下一个任务节点的候选人
* @param nodeName
* @return
*/
public List<Candidate> getNextTaskModelCandidates(String nodeName) {
List<Candidate> res = new ArrayList<>();
List<TaskModel> nextTaskModels = getNextTaskModels(nodeName);
nextTaskModels.forEach(item->{
res.addAll(getCandidates(item));
});
return res;
}
/**
* 根据任务模型获取候选人
* @param taskModel
* @return
*/
public List<Candidate> getCandidates(TaskModel taskModel) {
List<Candidate> res = new ArrayList<>();
// 从上下文中查找候选人处理人
List<CandidateHandler> handlerList = ServiceContext.findList(CandidateHandler.class);
handlerList.forEach(handler->{
// 通过候选从处理类获取候选人集合
List<Candidate> candidateList = handler.handle(taskModel);
if(candidateList!=null) {
res.addAll(candidateList);
}
});
// 通过候选人处理类获取修选人
String candidateHandler = taskModel.getCandidateHandler();
if(StrUtil.isNotEmpty(candidateHandler)) {
CandidateHandler candidateHandlerClass = ReflectUtil.newInstance(candidateHandler);
List<Candidate> candidateList = candidateHandlerClass.handle(taskModel);
if(candidateList!=null) {
res.addAll(candidateList);
}
}
// 去重
return res.stream().distinct().collect(Collectors.toList());
}
/**
* 根据指定的节点类型返回流程定义中所有模型对象
* @param clazz 节点类型
* @param <T> 泛型
* @return 节点列表
*/
public <T> List<T> getModels(Class<T> clazz) {
List<T> models = new ArrayList<T>();
buildModels(models, getStart().getNextModels(clazz), clazz);
return models;
}
private <T> void buildModels(List<T> models, List<T> nextModels, Class<T> clazz) {
for(T nextModel : nextModels) {
if(!models.contains(nextModel)) {
models.add(nextModel);
buildModels(models, ((NodeModel)nextModel).getNextModels(clazz), clazz);
}
}
}
}
@@ -0,0 +1,27 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
* 开始模型
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class StartModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行开始节点自定义执行逻辑
System.out.println(super.toString());
// 发布流程实例开始事件
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_INSTANCE_START).sourceId(execution.getProcessInstanceId()).build());
runOutTransition(execution);
}
}
@@ -0,0 +1,21 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.core.Execution;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 子流程模型
* @author mldong
* @date 2023/12/7
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class SubProcessModel extends NodeModel{
private String form;
private Integer version;
@Override
void exec(Execution execution) {
runOutTransition(execution);
}
}
@@ -0,0 +1,58 @@
package com.budwk.app.flow.engine.model;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.impl.CountersignHandler;
import com.budwk.app.flow.enums.CountersignTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class TaskModel extends NodeModel {
private String form; // 表单标识
private String assignee; // 参与人
private String assignmentHandler; // 参与人处理类
private ProcessTaskTypeEnum taskType; // 任务类型(主办/协办)
private ProcessTaskPerformTypeEnum performType; // 参与类型(普通参与/会签参与)
private String reminderTime; // 提醒时间
private String reminderRepeat; // 重复提醒间隔
private String expireTime; // 期待任务完成时间变量key
private String autoExecute; // 到期是否自动执行Y/N
private String callback; // 自动执行回调类
private Dict ext = Dict.create(); // 自定义扩展属性
// 候选用户标识
private String candidateUsers; // ext.getStr("candidateUsers");
// 候选用户组标识
private String candidateGroups; // ext.getStr("candidateGroups");
// 候选用户处理类字符串
private String candidateHandler; // ext.getStr("candidateHandler");
// 会签类型 PARALLEL表示并行会签SEQUENTIAL表示串行会签
private CountersignTypeEnum countersignType;
// 会签完成条件
/**
* 全部完成为空
* 按数量通过#nrOfCompletedInstances==n这里表示n人完成任务会签结束
* 按比例通过#nrOfCompletedInstances/nrOfInstances==n这里表示已完成会签数与总实例数达到一定比例时会签结束
* 一票通过#nrOfCompletedInstances==1这里表示1人完成任务会签结束
* 一票否决ONE_VOTE_VETO
*/
private String countersignCompletionCondition;
@Override
public void exec(Execution execution) {
// 执行任务节点自定义执行逻辑
System.out.println(super.toString());
if (ProcessTaskPerformTypeEnum.COUNTERSIGN.equals(performType)) {
// 会签任务处理
fire(new CountersignHandler(this), execution);
if (execution.isMerged()) {
runOutTransition(execution);
}
} else {
runOutTransition(execution);
}
}
}
@@ -0,0 +1,38 @@
package com.budwk.app.flow.engine.model;
import com.budwk.app.flow.engine.Action;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.handlers.impl.CreateTaskHandler;
import com.budwk.app.flow.engine.handlers.impl.StartSubProcessHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
* 边模型
* @author mldong
* @date 2023/4/25
*/
@EqualsAndHashCode(callSuper = true, exclude = {"source", "target"})
@Data
public class TransitionModel extends BaseModel implements Action {
private NodeModel source; // 边源节点引用
private NodeModel target; // 边目标节点引用
private String to; // 目标节点名称
private String expr; // 边表达式
private String g; // 边点坐标集合(x1,y1;x2,y2,x3,y3)开始拐角结束
private boolean enabled; // 是否可执行
@Override
public void execute(Execution execution) {
if(!enabled) return;
if(target instanceof TaskModel) {
// 创建阻塞任务
fire(new CreateTaskHandler((TaskModel) target),execution);
} else if(target instanceof SubProcessModel){
// 如果为子流程则启动子流程
fire(new StartSubProcessHandler((SubProcessModel) target), execution);
} else {
target.execute(execution);
}
}
}
@@ -0,0 +1,25 @@
package com.budwk.app.flow.engine.model.logicflow;
import cn.hutool.core.lang.Dict;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
*
* logicFlow边
* @author mldong
* @date 2023/4/26
*/
@Data
public class LfEdge implements Serializable {
private String id; // 边唯一id
private String type; // 边类型
private String sourceNodeId; // 源节点id
private String targetNodeId; // 目标节点id
private Dict properties; // 边属性
private Dict text; // 边文本
private LfPoint startPoint; // 边开始点坐标
private LfPoint endPoint; // 边结束点坐标
private List<LfPoint> pointsList; // 边所有点集合
}
@@ -0,0 +1,23 @@
package com.budwk.app.flow.engine.model.logicflow;
import com.budwk.app.flow.engine.model.BaseModel;
import lombok.Data;
import java.util.List;
/**
*
* logicFlow模型
* @author mldong
* @date 2023/4/26
*/
@Data
public class LfModel extends BaseModel {
private String type; // 流程定义分类
private String expireTime;// 过期时间常量或变量
private String instanceUrl; // 启动实例的url,前后端分离后定义为路由名或或路由地址
private String instanceNoClass; // 启动流程时流程实例的流水号生成类
private String preInterceptors; // 节点前置拦截器
private String postInterceptors; // 节点后置拦截器
private List<LfNode> nodes; // 节点集合
private List<LfEdge> edges; // 边集合
}
@@ -0,0 +1,21 @@
package com.budwk.app.flow.engine.model.logicflow;
import cn.hutool.core.lang.Dict;
import lombok.Data;
import java.io.Serializable;
/**
*
* logicFlow节点
* @author mldong
* @date 2023/4/26
*/
@Data
public class LfNode implements Serializable {
private String id; // 节点唯一id
private String type; // 节点类型
private int x; // 节点中心点x轴坐标
private int y; // 节点中心点y轴坐标
Dict properties; // 节点属性
Dict text; // 节点文本
}
@@ -0,0 +1,16 @@
package com.budwk.app.flow.engine.model.logicflow;
import lombok.Data;
import java.io.Serializable;
/**
*
* logicFlow坐标
* @author mldong
* @date 2023/4/26
*/
@Data
public class LfPoint implements Serializable {
private int x; // x轴坐标
private int y; // y轴坐标
}
@@ -0,0 +1,106 @@
package com.budwk.app.flow.engine.parser;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.TransitionModel;
import com.budwk.app.flow.engine.model.logicflow.*;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* 通用属性解析基本属性和边
* @author mldong
* @date 2023/4/26
*/
public abstract class AbstractNodeParser implements NodeParser {
// 节点模型对象
protected NodeModel nodeModel;
@Override
public void parse(LfNode lfNode, List<LfEdge> edges) {
nodeModel = newModel();
// 解析基本信息
nodeModel.setName(lfNode.getId());
if(ObjectUtil.isNotNull(lfNode.getText())) {
nodeModel.setDisplayName(lfNode.getText().getStr(TEXT_VALUE_KEY));
}
Dict properties = lfNode.getProperties();
// 解析布局属性
int x = lfNode.getX();
int y = lfNode.getY();
int w = Convert.toInt(properties.get(WIDTH_KEY),0);
int h = Convert.toInt(properties.get(HEIGHT_KEY),0);
nodeModel.setLayout(StrUtil.format("{},{},{},{}",x,y,w,h));
// 解析拦截器
nodeModel.setPreInterceptors(properties.getStr(PRE_INTERCEPTORS_KEY));
nodeModel.setPostInterceptors(properties.getStr(POST_INTERCEPTORS_KEY));
// 解析输出边
List<LfEdge> nodeEdges = getEdgeBySourceNodeId(lfNode.getId(), edges);
nodeEdges.forEach(edge->{
TransitionModel transitionModel = new TransitionModel();
transitionModel.setName(edge.getId());
transitionModel.setTo(edge.getTargetNodeId());
transitionModel.setSource(nodeModel);
transitionModel.setExpr(edge.getProperties().getStr(EXPR_KEY));
if(CollectionUtil.isNotEmpty(edge.getPointsList())) {
// x1,y1;x2,y2;x3,y3
transitionModel.setG(edge.getPointsList().stream().map(point->{
return point.getX()+","+point.getY();
}).collect(Collectors.joining(";")));
} else {
if(ObjectUtil.isNotNull(edge.getStartPoint()) && ObjectUtil.isNotNull(edge.getEndPoint())) {
int startPointX = edge.getStartPoint().getX();
int startPointY = edge.getStartPoint().getY();
int endPointX = edge.getEndPoint().getX();
int endPointY = edge.getEndPoint().getY();
transitionModel.setG(StrUtil.format("{},{};{},{}", startPointX, startPointY, endPointX, endPointY));
}
}
nodeModel.getOutputs().add(transitionModel);
});
// 调用子类特定解析方法
parseNode(lfNode);
}
/**
* 子类实现此类完成特定解析
* @param lfNode
*/
public abstract void parseNode(LfNode lfNode);
/**
* 由子类各自创建节点模型对象
* @return
*/
public abstract NodeModel newModel();
@Override
public NodeModel getModel() {
return nodeModel;
}
/**
* 获取节点输入
* @param targetNodeId 目标节点id
* @param edges
* @return
*/
private List<LfEdge> getEdgeByTargetNodeId(String targetNodeId,List<LfEdge> edges) {
return edges.stream().filter(edge->{
return edge.getTargetNodeId().equals(targetNodeId);
}).collect(Collectors.toList());
}
/**
* 获取节点输出
* @param sourceNodeId 源节点id
* @param edges
* @return
*/
private List<LfEdge> getEdgeBySourceNodeId(String sourceNodeId,List<LfEdge> edges) {
return edges.stream().filter(edge->{
return edge.getSourceNodeId().equals(sourceNodeId);
}).collect(Collectors.toList());
}
}
@@ -0,0 +1,112 @@
package com.budwk.app.flow.engine.parser;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.engine.model.TransitionModel;
import com.budwk.app.flow.engine.model.logicflow.*;
import java.io.ByteArrayInputStream;
import java.util.List;
public class ModelParser {
private ModelParser(){}
// /**
// * 将json定义文件解析成流程模型对象
// * @param bytes
// * @return
// */
// public static ProcessModel parse(byte [] bytes) {
// String json = IoUtil.readUtf8(new ByteArrayInputStream(bytes));
// LfModel lfModel = JSONUtil.parse(json).toBean(LfModel.class);
// ProcessModel processModel = new ProcessModel();
// List<LfNode> nodes = lfModel.getNodes();
// List<LfEdge> edges = lfModel.getEdges();
// if(CollectionUtil.isEmpty(nodes) || CollectionUtil.isEmpty(edges) ) {
// return processModel;
// }
// // 流程定义基本信息
// processModel.setName(lfModel.getName());
// processModel.setDisplayName(lfModel.getDisplayName());
// processModel.setType(lfModel.getType());
// processModel.setInstanceUrl(lfModel.getInstanceUrl());
// processModel.setInstanceNoClass(lfModel.getInstanceNoClass());
// processModel.setPostInterceptors(lfModel.getPostInterceptors());
// processModel.setPreInterceptors(lfModel.getPreInterceptors());
// // 流程节点信息
// nodes.forEach(node->{
// String type = node.getType().replace(NodeParser.NODE_NAME_PREFIX,"");
// NodeParser nodeParser = ServiceContext.findByName(type,NodeParser.class);
// if(nodeParser!=null) {
// nodeParser.parse(node, edges);
// NodeModel nodeModel = nodeParser.getModel();
// processModel.getNodes().add(nodeParser.getModel());
// if (nodeModel instanceof TaskModel) {
// processModel.getTasks().add((TaskModel) nodeModel);
// }
// }
// });
// // 循环节点模型构造输入边输出边的sourcetarget
// for(NodeModel node : processModel.getNodes()) {
// for(TransitionModel transition : node.getOutputs()) {
// String to = transition.getTo();
// for(NodeModel node2 : processModel.getNodes()) {
// if(to.equalsIgnoreCase(node2.getName())) {
// node2.getInputs().add(transition);
// transition.setTarget(node2);
// }
// }
// }
// }
// return processModel;
// }
public static ProcessModel parse(String json) {
LfModel lfModel = JSONUtil.parse(json).toBean(LfModel.class);
ProcessModel processModel = new ProcessModel();
List<LfNode> nodes = lfModel.getNodes();
List<LfEdge> edges = lfModel.getEdges();
if(CollectionUtil.isEmpty(nodes) || CollectionUtil.isEmpty(edges) ) {
return processModel;
}
// 流程定义基本信息
processModel.setName(lfModel.getName());
processModel.setDisplayName(lfModel.getDisplayName());
processModel.setType(lfModel.getType());
processModel.setInstanceUrl(lfModel.getInstanceUrl());
processModel.setInstanceNoClass(lfModel.getInstanceNoClass());
processModel.setPostInterceptors(lfModel.getPostInterceptors());
processModel.setPreInterceptors(lfModel.getPreInterceptors());
// 流程节点信息
nodes.forEach(node->{
String type = node.getType().replace(NodeParser.NODE_NAME_PREFIX,"");
NodeParser nodeParser = ServiceContext.findByName(type,NodeParser.class);
if(nodeParser!=null) {
nodeParser.parse(node, edges);
NodeModel nodeModel = nodeParser.getModel();
processModel.getNodes().add(nodeParser.getModel());
if (nodeModel instanceof TaskModel) {
processModel.getTasks().add((TaskModel) nodeModel);
}
}
});
// 循环节点模型构造输入边输出边的sourcetarget
for(NodeModel node : processModel.getNodes()) {
for(TransitionModel transition : node.getOutputs()) {
String to = transition.getTo();
for(NodeModel node2 : processModel.getNodes()) {
if(to.equalsIgnoreCase(node2.getName())) {
node2.getInputs().add(transition);
transition.setTarget(node2);
}
}
}
}
return processModel;
}
}
@@ -0,0 +1,56 @@
package com.budwk.app.flow.engine.parser;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.logicflow.LfEdge;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import java.util.List;
/**
*
* 节点解析接口
* @author mldong
* @date 2023/4/26
*/
public interface NodeParser {
String NODE_NAME_PREFIX="snaker:"; // 节点名称前辍
String TEXT_VALUE_KEY = "value"; // 文本值
String WIDTH_KEY = "width"; // 节点宽度
String HEIGHT_KEY = "height"; // 节点高度
String PRE_INTERCEPTORS_KEY = "preInterceptors"; // 前置拦截器
String POST_INTERCEPTORS_KEY = "postInterceptors"; // 后置拦截器
String EXPR_KEY = "expr"; // 表达式key
String HANDLE_CLASS_KEY = "handleClass"; // 表达式处理类
String FORM_KEY = "form"; // 表单标识
String ASSIGNEE_KEY = "assignee"; // 参与人
String ASSIGNMENT_HANDLE_KEY = "assignmentHandler"; // 参与人处理类
String TASK_TYPE_KEY = "taskType"; // 任务类型(主办/协办)
String PERFORM_TYPE_KEY = "performType"; // 参与类型(普通参与/会签参与)
String REMINDER_TIME_KEY = "reminderTime"; // 提醒时间
String REMINDER_REPEAT_KEY = "reminderRepeat"; // 重复提醒间隔
String EXPIRE_TIME_KEY = "expireTime"; // 期待任务完成时间变量key
String AUTH_EXECUTE_KEY = "autoExecute"; // 到期是否自动执行Y/N
String CALLBACK_KEY = "callback"; // 自动执行回调类
String EXT_FIELD_KEY = "field"; // 自定义扩展属性
String EXT_FIELD_CANDIDATE_USERS_KET = "candidateUsers";
String EXT_FIELD_CANDIDATE_GROUPS_KEY = "candidateGroups";
String EXT_FIELD_CANDIDATE_HANDLER_KEY = "candidateHandler";
String EXT_FIELD_COUNTERSIGN_TYPE_KEY = "countersignType"; // 会签类型
String EXT_FIELD_COUNTERSIGN_COMPLETION_CONDITION_KEY = "countersignCompletionCondition"; // 会签完成条件
String CLASS_KEY = "clazz"; // 类路径
String METHOD_NAME_KEY = "methodName"; // 方法名
String ARGS_KEY = "args"; // 方法入参
String RETURN_VAL_KEY = "val"; // 返回变量名
String VERSION_KEY = "version"; // 版本号
/**
* 节点属性解析方法由解析类完成解析
* @param lfNode LogicFlow节点对象
* @param edges 所有边对象
*/
void parse(LfNode lfNode, List<LfEdge> edges);
/**
* 解析完成后提供返回NodeModel对象
* @return 节点模型
*/
NodeModel getModel();
}
@@ -0,0 +1,29 @@
package com.budwk.app.flow.engine.parser.impl;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.model.CustomModel;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
* 自定义节点解析类
* @author mldong
* @date 2023/12/7
*/
public class CustomParser extends AbstractNodeParser {
@Override
public void parseNode(LfNode lfNode) {
CustomModel customModel = (CustomModel) nodeModel;
Dict properties = lfNode.getProperties();
customModel.setClazz(properties.getStr(CLASS_KEY));
customModel.setMethodName(properties.getStr(METHOD_NAME_KEY));
customModel.setArgs(properties.getStr(ARGS_KEY));
customModel.setVar(properties.get(RETURN_VAL_KEY, FlowConst.CUSTOM_RETURN_VAL));
}
@Override
public NodeModel newModel() {
return new CustomModel();
}
}
@@ -0,0 +1,33 @@
package com.budwk.app.flow.engine.parser.impl;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.engine.model.DecisionModel;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
*
* 决策节点解析类
* @author mldong
* @date 2023/4/26
*/
public class DecisionParser extends AbstractNodeParser {
/**
* 解析decision节点特有属性
* @param lfNode
*/
@Override
public void parseNode(LfNode lfNode) {
DecisionModel decisionModel = (DecisionModel) nodeModel;
Dict properties = lfNode.getProperties();
decisionModel.setExpr(properties.getStr(EXPR_KEY));
decisionModel.setHandleClass(properties.getStr(HANDLE_CLASS_KEY));
}
@Override
public NodeModel newModel() {
return new DecisionModel();
}
}
@@ -0,0 +1,25 @@
package com.budwk.app.flow.engine.parser.impl;
import com.budwk.app.flow.engine.model.EndModel;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
*
* 结束节点解析类
* @author mldong
* @date 2023/4/26
*/
public class EndParser extends AbstractNodeParser {
@Override
public void parseNode(LfNode lfNode) {
}
@Override
public NodeModel newModel() {
return new EndModel();
}
}
@@ -0,0 +1,26 @@
package com.budwk.app.flow.engine.parser.impl;
import com.budwk.app.flow.engine.model.ForkModel;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
*
* 分支节点解析类
* @author mldong
* @date 2023/4/26
*/
public class ForkParser extends AbstractNodeParser {
@Override
public void parseNode(LfNode lfNode) {
}
@Override
public NodeModel newModel() {
return new ForkModel();
}
}
@@ -0,0 +1,26 @@
package com.budwk.app.flow.engine.parser.impl;
import com.budwk.app.flow.engine.model.JoinModel;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
*
* 合并节点解析器
* @author mldong
* @date 2023/4/26
*/
public class JoinParser extends AbstractNodeParser {
@Override
public void parseNode(LfNode lfNode) {
}
@Override
public NodeModel newModel() {
return new JoinModel();
}
}
@@ -0,0 +1,24 @@
package com.budwk.app.flow.engine.parser.impl;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.StartModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
*
* 开始节点解析类
* @author mldong
* @date 2023/4/26
*/
public class StartParser extends AbstractNodeParser {
@Override
public void parseNode(LfNode lfNode) {
}
@Override
public NodeModel newModel() {
return new StartModel();
}
}
@@ -0,0 +1,75 @@
package com.budwk.app.flow.engine.parser.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ReflectUtil;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.engine.model.logicflow.*;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
import com.budwk.app.flow.enums.CountersignTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskTypeEnum;
/**
*
* 任务节点解析类
* @author mldong
* @date 2023/4/26
*/
public class TaskParser extends AbstractNodeParser {
/**
* 解析task节点特有属性
* @param lfNode
*/
@Override
public void parseNode(LfNode lfNode) {
TaskModel taskModel = (TaskModel)nodeModel;
Dict properties = lfNode.getProperties();
taskModel.setForm(properties.getStr(FORM_KEY));
taskModel.setAssignee(properties.getStr(ASSIGNEE_KEY));
taskModel.setAssignmentHandler(properties.getStr(ASSIGNMENT_HANDLE_KEY));
taskModel.setTaskType(ProcessTaskTypeEnum.codeOf(properties.get(TASK_TYPE_KEY)));
taskModel.setPerformType(ProcessTaskPerformTypeEnum.codeOf(properties.get(PERFORM_TYPE_KEY)));
taskModel.setReminderTime(properties.getStr(REMINDER_TIME_KEY));
taskModel.setReminderRepeat(properties.getStr(REMINDER_REPEAT_KEY));
taskModel.setExpireTime(properties.getStr(EXPIRE_TIME_KEY));
taskModel.setAutoExecute(properties.getStr(AUTH_EXECUTE_KEY));
taskModel.setCallback(properties.getStr(CALLBACK_KEY));
// 解析候选人属性
taskModel.setCandidateUsers(properties.getStr(EXT_FIELD_CANDIDATE_USERS_KET));
taskModel.setCandidateGroups(properties.getStr(EXT_FIELD_CANDIDATE_GROUPS_KEY));
taskModel.setCandidateHandler(properties.getStr(EXT_FIELD_CANDIDATE_HANDLER_KEY));
// 解析会签属性
taskModel.setCountersignType(CountersignTypeEnum.codeOf(properties.getStr(EXT_FIELD_COUNTERSIGN_TYPE_KEY)));
taskModel.setCountersignCompletionCondition(properties.getStr(EXT_FIELD_COUNTERSIGN_COMPLETION_CONDITION_KEY));
// 自定义扩展属性
Object field = properties.get(EXT_FIELD_KEY);
if(field!=null) {
Dict ext = Convert.convert(Dict.class, field);
taskModel.setExt(ext);
// 解析候选人属性
taskModel.setCandidateUsers(properties.get(EXT_FIELD_CANDIDATE_USERS_KET,ext.getStr(EXT_FIELD_CANDIDATE_USERS_KET)));
taskModel.setCandidateGroups(properties.get(EXT_FIELD_CANDIDATE_GROUPS_KEY,ext.getStr(EXT_FIELD_CANDIDATE_GROUPS_KEY)));
taskModel.setCandidateHandler(properties.get(EXT_FIELD_CANDIDATE_HANDLER_KEY,ext.getStr(EXT_FIELD_CANDIDATE_HANDLER_KEY)));
// 解析会签属性
taskModel.setCountersignType(CountersignTypeEnum.codeOf(properties.get(EXT_FIELD_COUNTERSIGN_TYPE_KEY,ext.getStr(EXT_FIELD_COUNTERSIGN_TYPE_KEY))));
taskModel.setCountersignCompletionCondition(properties.get(EXT_FIELD_COUNTERSIGN_COMPLETION_CONDITION_KEY,ext.getStr(EXT_FIELD_COUNTERSIGN_COMPLETION_CONDITION_KEY)));
} else {
taskModel.setExt(Dict.create());
}
// 将其他properties添加到ext中
properties.forEach((k,v)->{
if(!ReflectUtil.hasField(TaskModel.class,k)){
taskModel.getExt().set(k,v);
}
});
}
@Override
public NodeModel newModel() {
return new TaskModel();
}
}
@@ -0,0 +1,28 @@
package com.budwk.app.flow.engine.parser.impl;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.SubProcessModel;
import com.budwk.app.flow.engine.model.logicflow.LfNode;
import com.budwk.app.flow.engine.parser.AbstractNodeParser;
/**
* 子流程解析类
* @author mldong
* @date 2023/12/7
*/
public class WfSubProcessParser extends AbstractNodeParser {
@Override
public void parseNode(LfNode lfNode) {
SubProcessModel subProcessModel = (SubProcessModel) nodeModel;
Dict properties = lfNode.getProperties();
subProcessModel.setForm(properties.getStr(FORM_KEY));
subProcessModel.setVersion(properties.getInt(VERSION_KEY));
}
@Override
public NodeModel newModel() {
return new SubProcessModel();
}
}
@@ -0,0 +1,24 @@
package com.budwk.app.flow.engine.scheduling;
import cn.hutool.core.lang.Dict;
/**
* 任务调度接口增加删除任务
* @author mldong
* @date 2023/12/4
*/
public interface IScheduler {
String SOURCE_ID_KEY = "sourceId";
String SOURCE_TYPE_KEY = "sourceType";
/**
* 添加作业到调度器
* @param args
*/
void addJob(String jobId,Dict args);
/**
* 从调度器中删除作业
* @param jobId
*/
void removeJob(String jobId);
}
@@ -0,0 +1,44 @@
package com.budwk.app.flow.engine.scheduling;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessEventListener;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
/**
* @author mldong
* @date 2023/12/5
*/
@IocBean
public class SchedulerProcessEventListener implements ProcessEventListener {
@Override
public void onEvent(ProcessEvent event) {
List<IScheduler> schedulerList = ServiceContext.findList(IScheduler.class);
if(CollectionUtil.newArrayList(
ProcessEventTypeEnum.PROCESS_INSTANCE_START,
ProcessEventTypeEnum.PROCESS_TASK_START
).contains(event.getEventType())) {
// 流程实例开始事件流程任务开始事件添加作业到调度器
schedulerList.forEach(scheduler->{
scheduler.addJob(event.getEventType().name()+"_"+event.getSourceId(),
Dict.of(
IScheduler.SOURCE_ID_KEY,event.getSourceId(),
IScheduler.SOURCE_TYPE_KEY,event.getEventType().getCode()
));
});
} else if(CollectionUtil.newArrayList(
ProcessEventTypeEnum.PROCESS_INSTANCE_END,
ProcessEventTypeEnum.PROCESS_TASK_END
).contains(event.getEventType())) {
// 流程实例结束事件流程任务结束事件从调度器移除作业
schedulerList.forEach(scheduler->{
scheduler.removeJob(event.getEventType().name()+"_"+event.getSourceId());
});
}
}
}
@@ -0,0 +1,137 @@
package com.budwk.app.flow.engine.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.expression.ExpressionUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.sys.views.View_user;
import org.dromara.warm.flow.core.exception.FlowException;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 流程工具
*
* @author mldong
* @date 2023/10/7
*/
public class FlowUtil {
private FlowUtil() {
}
/**
* 追加用户信息到变量中
*
* @param operator
* @param args
*/
public static void addUserInfoToArgs(String operator, Dict args) {
Dao dao = ServiceContext.find(Dao.class);
View_user user = dao.fetch(View_user.class, Cnd.where(View_user::getId, "=", operator));
if (user == null) {
throw new FlowException("用户不存在:" + operator);
}
args.put(FlowConst.USER_USER_ID, operator);
args.put(FlowConst.INITIATOR, operator);
args.put(FlowConst.INITIATOR_NAME, user.getUsername());
args.put(FlowConst.INITIATOR_ACCOUNT, user.getLoginname());
args.put(FlowConst.INITIATOR_UNIT_ID, user.getUnitId());
args.put(FlowConst.INITIATOR_UNIT_NAME, user.getUnitName());
args.put(FlowConst.INITIATOR_UNIT_UNION_ID, user.getUnionId());
args.put(FlowConst.INITIATOR_UNIT_UNION_NAME, user.getUnionName());
}
/**
* 增加自动构造标题
*
* @param args
*/
public static void addAutoGenTitle(String processDefineDisplayName, Dict args) {
// 申请人的xx流程-日期
// String format = "{}的{}-{}";
// args.put(FlowConst.AUTO_GEN_TITLE, StrUtil.format(format, args.getStr(FlowConst.USER_REAL_NAME), processDefineDisplayName,
// DateUtil.format(new Date(), "yyyy-MM-dd HH:mm")));
args.put(FlowConst.PROCESS_INSTANCE_NAME, args.getStr(FlowConst.INITIATOR_NAME) + "" + processDefineDisplayName + "申请");
}
/**
* 参数转字典
*
* @param variable
* @return
*/
public static Dict variableToDict(String variable) {
if (JSONUtil.isTypeJSON(variable)) {
return Dict.parse(JSONUtil.parseObj(variable));
}
return Dict.create();
}
/**
* 判断是否为第一个任务节点
*
* @param processModel
* @param taskName
* @return
*/
public static boolean isFistTaskName(ProcessModel processModel, String taskName) {
AtomicBoolean atomicBoolean = new AtomicBoolean(false);
processModel.getStart().getOutputs().forEach(nodeModel -> {
if (nodeModel.getTo().equalsIgnoreCase(taskName)) {
atomicBoolean.set(true);
}
});
return atomicBoolean.get();
}
/**
* 解析日期
*
* @param expireTime
* @param args
* @return
*/
public static Date processTime(String expireTime, Dict args) {
// 如果变量中存在则使用变量中的时间
if (args.containsKey(expireTime)) {
Object obj = args.get(expireTime);
if (obj instanceof Date) {
return Convert.toDate(obj);
} else if (obj instanceof Long) {
return new Date(Convert.toLong(obj));
} else if (obj instanceof String) {
return DateUtil.parseDateTime(Convert.toStr(obj));
}
}
if (StrUtil.isNotBlank(expireTime)) {
if (expireTime.contains("s")) {
return DateUtil.offsetSecond(new Date(), Convert.toInt(expireTime.substring(0, expireTime.length() - 1)));
} else if (expireTime.contains("m")) {
return DateUtil.offsetMinute(new Date(), Convert.toInt(expireTime.substring(0, expireTime.length() - 1)));
} else if (expireTime.contains("h")) {
return DateUtil.offsetHour(new Date(), Convert.toInt(expireTime.substring(0, expireTime.length() - 1)));
} else if (expireTime.contains("d")) {
return DateUtil.offsetDay(new Date(), Convert.toInt(expireTime.substring(0, expireTime.length() - 1)));
}
return DateUtil.parseDateTime(expireTime);
}
return null;
}
}
@@ -0,0 +1,50 @@
package com.budwk.app.flow.entity;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import lombok.Builder;
import lombok.Data;
import java.util.Objects;
/**
*
* 候选人实体
* @author mldong
* @date 2023/6/26
*/
@Data
@Builder
public class Candidate {
// 用户ID
private String userId;
// 用户名
private String userName;
// 扩展属性
private Dict ext = Dict.create();
/**
* 重写equals方便去重
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Candidate) {
Candidate tmp = (Candidate) obj;
if (ObjectUtil.equals(this.getUserId(),tmp.getUserId())) {
return true;
}
}
return false;
}
/**
* 重写hashCode方便去重
* @return
*/
@Override
public int hashCode() {
return Objects.hash(userId);
}
}
@@ -0,0 +1,40 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
/**
* <p>
* 流程实例抄送
* </p>
*
* @author mldong
* @since 2023-12-05
*/
@Getter
@Setter
@Table("wf_process_cc_instance")
public class ProcessCcInstance extends BaseModel {
@Id
private Long id;
@Comment("流程实例ID")
@Column
private Long processInstanceId;
@Comment("被抄送人ID")
@Column
private String actorId;
@Comment("抄送状态(1:已读;0:未读)")
@Column
private Integer state;
}
@@ -0,0 +1,67 @@
package com.budwk.app.flow.entity;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.*;
/**
* <p>
* 流程定义
* </p>
*
* @author mldong
* @since 2023-09-26
*/
@Getter
@Setter
@Table("wf_process_define")
@Comment("流程定义")
public class ProcessDefine extends BaseModel {
@Id
private Long id;
@Comment("唯一编码")
@Column
private String name;
@Comment("显示名称")
@Column
private String displayName;
@Comment("流程类型")
@Column
private String type;
@Comment("流程分类")
@Column
private String category;
@Comment("电脑端发起地址")
@Column
private String instanceUrl;
@Comment("手机端发起地址")
@Column
private String h5InstanceUrl;
@Comment("图标")
@Column
private String icon;
@Comment("流程是否可用(1可用;0不可用)")
@Column
private Integer state;
@Comment("流程模型定义")
@Column
@ColDefine(type = ColType.VARCHAR)
private JSONObject content;
@Comment("版本")
@Column
private Integer version;
}
@@ -0,0 +1,70 @@
package com.budwk.app.flow.entity;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
/**
* <p>
* 流程设计
* </p>
*
* @author mldong
* @since 2023-10-12
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Table("wf_process_design")
@Comment("流程设计")
public class ProcessDesign extends BaseModel {
private static final long serialVersionUID = 1L;
@Comment("主键")
@Id
private Long id;
@Comment("唯一编码")
@Column
private String name;
@Comment("显示名称")
@Column
private String displayName;
@Comment("流程分类")
@Column
private String type;
@Comment("流程分类")
@Column
private String category;
@Comment("电脑端发起地址")
@Column
private String instanceUrl;
@Comment("手机端发起地址")
@Column
private String h5InstanceUrl;
@Comment("图标")
@Column
private String icon;
@Comment("是否已部署")
@Column
private Integer isDeployed;
@Comment("备注")
@Column
private String remark;
@Comment("流程定义")
@Column
@ColDefine(type = ColType.MYSQL_JSON)
private JSONObject content;
}
@@ -0,0 +1,39 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import java.util.Date;
/**
* <p>
* 流程设计历史
* </p>
*
* @author mldong
* @since 2023-09-26
*/
@Getter
@Setter
@Table("wf_process_design_his")
@Comment("流程设计历史")
public class ProcessDesignHis extends BaseModel {
@Comment("主键")
@Id
private Long id;
@Comment("流程设计ID")
@Column
private Long processDesignId;
@Comment("流程模型定义")
@Column
private byte[] content;
}
@@ -0,0 +1,64 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 流程实例
* </p>
*
* @author mldong
* @since 2023-09-26
*/
@Getter
@Setter
@Table("wf_process_instance")
@Comment("流程实例")
public class ProcessInstance extends BaseModel {
@Comment( "主键")
@Id
private Long id;
@Comment( "父流程ID,子流程实例才有值")
@Column
private Long parentId;
@Comment( "流程定义ID")
@Column
private Long processDefineId;
@Comment( "流程实例状态(10:进行中;20:已完成;30:已撤回;40:强行中止;50:挂起;99:已废弃)")
@Column
private Integer state;
@Comment( "父流程依赖的节点名称")
@Column
private String parentNodeName;
@Comment( "业务编号")
@Column
private String businessNo;
@Comment( "流程发起人")
@Column
private String operator;
@Comment( "期望完成时间")
@Column
private Date expireTime;
@Comment( "附属变量json存储")
@Column
private String variable;
}
@@ -0,0 +1,56 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 流程委托代理
* </p>
*
* @author mldong
* @since 2023-12-06
*/
@Getter
@Setter
@Table("wf_process_surrogate")
@Comment("流程委托代理")
public class ProcessSurrogate extends BaseModel {
@Comment( "主键")
@Id
private Long id;
@Comment( "流程名称")
@Column
private String processName;
@Comment( "授权人")
@Column
private String operator;
@Comment( "代理人")
@Column
private String surrogate;
@Comment( "授权开始时间")
@Column
private Date startTime;
@Comment( "授权结束时间")
@Column
private Date endTime;
@Comment( "是否启用")
@Column
private Integer enabled;
}
@@ -0,0 +1,80 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Getter;
import lombok.Setter;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 流程任务
* </p>
*
* @author mldong
* @since 2023-09-26
*/
@Getter
@Setter
@Table("wf_process_task")
@Comment("流程任务")
public class ProcessTask extends BaseModel {
@Comment("主键")
@Id
private Long id;
@Comment("流程实例ID")
@Column
private Long processInstanceId;
@Comment("任务名称编码")
@Column
private String taskName;
@Comment("任务显示名称")
@Column
private String displayName;
@Comment("任务类型(0:主办任务;1:协办任务)")
@Column
private Integer taskType;
@Comment("参与类型(0:普通参与;1:会签参与)")
@Column
private Integer performType;
@Comment("任务状态(10:进行中;20:已完成;30:已撤回;40:强行中止;50:挂起;99:已废弃)")
@Column
private Integer taskState;
@Comment("任务处理人")
@Column
private String operator;
@Comment("任务完成时间")
@Column
private Date finishTime;
@Comment("任务期待完成时间")
@Column
private Date expireTime;
@Comment("任务处理表单KEY")
@Column
private String formKey;
@Comment("父任务ID")
@Column
private Long taskParentId;
@Comment("附属变量json存储")
@Column
private String variable;
}
@@ -0,0 +1,37 @@
package com.budwk.app.flow.entity;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
/**
* <p>
* 流程任务和参与人关系
* </p>
*
* @author mldong
* @since 2023-09-26
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Table("wf_process_task_actor")
@Comment("流程任务和参与人关系")
public class ProcessTaskActor extends BaseModel {
@Comment("主键")
@Id
private Long id;
@Comment("流程任务ID")
@Column
private Long processTaskId;
@Comment("参与者ID")
@Column
private String actorId;
}
@@ -0,0 +1,39 @@
package com.budwk.app.flow.enums;
import cn.hutool.core.convert.Convert;
import java.util.Arrays;
/**
* @author mldong
* @date 2023/11/24
*/
//@DictEnum(key = "wf_countersign_type", name = "会签类型")
public enum CountersignTypeEnum {
PARALLEL(0, "并行会签"),
SEQUENTIAL(1, "串行会签"),
;
private final Integer code;
private final String message;
public static CountersignTypeEnum codeOf(Object code) {
return Arrays.stream(CountersignTypeEnum.class.getEnumConstants()).filter(e ->
e.getCode().equals(Convert.toInt(code))
|| e.name().equalsIgnoreCase(Convert.toStr(code))
|| e.getMessage().equalsIgnoreCase(Convert.toStr(code))
).findAny().orElse(CountersignTypeEnum.PARALLEL);
}
CountersignTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
@@ -0,0 +1,30 @@
package com.budwk.app.flow.enums;
/**
* @author mldong
* @date 2023/12/5
*/
public enum ProcessEventTypeEnum{
PROCESS_INSTANCE_START(1,"流程实例开始事件"),
PROCESS_INSTANCE_END(2, "流程实例结束事件"),
PROCESS_TASK_START(3, "流程任务开始事件"),
PROCESS_TASK_END(4, "流程任务结束事件"),
;
private final Integer code;
private final String message;
ProcessEventTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
@@ -0,0 +1,28 @@
package com.budwk.app.flow.enums;
import com.budwk.app.base.annotation.DictEnum;
import lombok.Getter;
/**
* 流程实例状态(10进行中20已完成30已撤回40强行终止50挂起99已废弃)
*/
@Getter
@DictEnum(key = "ProcessInstanceStateEnum", name = "流程实例状态")
public enum ProcessInstanceStateEnum {
DOING(10, "进行中"),
FINISHED(20, "已完成"),
WITHDRAW(30, "已撤回"),
INTERRUPT(40, "强行终止"),
REJECT(45, "已拒绝"),
PENDING(50, "挂起"),
ABANDON(99, "已废弃");
private final Integer code;
private final String message;
ProcessInstanceStateEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
@@ -0,0 +1,31 @@
package com.budwk.app.flow.enums;
import lombok.Getter;
/**
* 流程提交类型操作类型
*
* @author mldong
* @date 2023/10/7
*/
@Getter
public enum ProcessSubmitTypeEnum {
APPLY(0, "发起申请"),
AGREE(1, "同意申请"),
REJECT(2, "拒绝申请"),
ROLLBACK(3, "退回上一步"),
JUMP(4, "跳转"),
RE_APPLY(5, "重新提交"),
ROLLBACK_TO_OPERATOR(6, "退回发起人"),
COUNTERSIGN_DISAGREE(20, "拒绝申请");
private final Integer code;
private final String message;
ProcessSubmitTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
@@ -0,0 +1,56 @@
package com.budwk.app.flow.enums;
import cn.hutool.core.convert.Convert;
/**
*
* 任务参与类型
* @author mldong
* @date 2023/4/25
*/
public enum ProcessTaskPerformTypeEnum {
/**
* 普通参与一个或多个人同时参与一个任务所有人只要其中一人执行完成就能驱动任务节点往下一步执行
*/
NORMAL(0,"普通参与"),
/**
* 会签参与给每个人都创建任务满足一定条件时才能驱动任务节点往下一步执行
* 1. 所有人都执行完成
* 2. 有一定比率执行完成时
* 3. 某特殊人员执行完成时
* 等等
*/
COUNTERSIGN(1, "会签参与"),
;
private final Integer code;
private final String message;
ProcessTaskPerformTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
/**
* code转成枚举
* @param code
* @return
*/
public static ProcessTaskPerformTypeEnum codeOf(Object code) {
if("ALL".equalsIgnoreCase(Convert.toStr(code))
|| COUNTERSIGN.toString().toUpperCase().equals(code)
|| COUNTERSIGN.toString().toLowerCase().equals(code)
|| COUNTERSIGN.code.equals(code)) {
return COUNTERSIGN;
}
return NORMAL;
}
}
@@ -0,0 +1,26 @@
package com.budwk.app.flow.enums;
import lombok.Getter;
/**
* 流程任务状态枚举
*/
@Getter
public enum ProcessTaskStateEnum {
DOING(10,"进行中"),
FINISHED(20, "已完成"),
WITHDRAW(30, "已撤回"),
INTERRUPT(40, "强行终止"),
PENDING(50,"挂起"),
ABANDON(99,"已废弃")
;
private final Integer code;
private final String message;
ProcessTaskStateEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
@@ -0,0 +1,49 @@
package com.budwk.app.flow.enums;
import cn.hutool.core.convert.Convert;
import java.util.Arrays;
/**
*
* 任务类型
* @author mldong
* @date 2023/4/25
*/
//@DictEnum(key = "wf_process_task_type", name = "流程任务类型")
public enum ProcessTaskTypeEnum {
MAJOR(0,"主办"),
SECONDARY(1, "协办"),
RECORD(2,"记录"),
;
private final Integer code;
private final String message;
ProcessTaskTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
/**
* code转成枚举
* @param code
* @return
*/
public static ProcessTaskTypeEnum codeOf(Object code) {
return Arrays.stream(ProcessTaskTypeEnum.class.getEnumConstants()).filter(e ->
e.getCode().equals(Convert.toInt(code))
|| e.name().equalsIgnoreCase(Convert.toStr(code))
|| e.getMessage().equalsIgnoreCase(Convert.toStr(code))
).findAny().orElse(ProcessTaskTypeEnum.MAJOR);
}
}
@@ -0,0 +1,31 @@
package com.budwk.app.flow.handler;
import com.budwk.app.flow.engine.AssignmentHandler;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.model.TaskModel;
import org.nutz.dao.Dao;
import java.util.List;
/**
* 获取申请人所在分工会的分工会主席
*/
public class FlowFghzxAssignmentHandler implements AssignmentHandler {
@Override
public List<String> assign(TaskModel model, Execution execution) {
Dao dao = ServiceContext.find(Dao.class);
return List.of();
}
@Override
public String getMessage() {
return "当前用户所属分工会主席";
}
@Override
public int getOrder() {
return 10;
}
}
@@ -0,0 +1,46 @@
//package com.budwk.app.flow.service;
//
//import com.baomidou.mybatisplus.extension.service.IService;
//import com.budwk.app.base.service.BaseService;
//import com.budwk.app.flow.entity.ProcessCcInstance;
//import com.mldong.base.CommonPage;
//import com.mldong.modules.wf.dto.ProcessCcInstancePageParam;
//import com.mldong.modules.wf.dto.ProcessCcInstanceParam;
//import com.mldong.modules.wf.entity.ProcessCcInstance;
//import com.mldong.modules.wf.vo.ProcessCcInstanceVO;
///**
// * <p>
// * 流程实例抄送 服务类
// * </p>
// *
// * @author mldong
// * @since 2023-12-05
// */
//public interface ProcessCcInstanceService extends BaseService<ProcessCcInstance> {
// /**
// * 添加流程实例抄送
// * @param param
// * @return
// */
// boolean save(ProcessCcInstanceParam param);
//
// /**
// * 更新流程实例抄送
// * @param param
// * @return
// */
// boolean update(ProcessCcInstanceParam param);
//
// /**
// * 自定义分页查询流程实例抄送
// * @param param
// * @return
// */
// CommonPage<ProcessCcInstanceVO> page(ProcessCcInstancePageParam param);
// /**
// * 通过id查询
// * @param id
// * @return
// */
// ProcessCcInstanceVO findById(Long id);
//}
@@ -0,0 +1,161 @@
package com.budwk.app.flow.service;
import cn.hutool.core.lang.Dict;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.enums.UpAndDownParam;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.vo.ProcessDefineVO;
import java.io.InputStream;
/**
* <p>
* 流程定义 服务类
* </p>
*
* @author mldong
* @since 2023-09-26
*/
public interface ProcessDefineService extends BaseService<ProcessDefine> {
/**
* 添加流程定义
* @param param
* @return
*/
boolean save(ProcessDefine param);
/**
* 更新流程定义
* @param param
* @return
*/
boolean update(ProcessDefine param);
/**
* 自定义分页查询流程定义
* @param param
* @return
*/
// CommonPage<ProcessDefineVO> page(ProcessDefinePageParam param);
/**
* 通过id查询
* @param id
* @return
*/
ProcessDefineVO findById(Long id);
/**
* 部署流程定义文件同name存在多个版本
* @param inputStream
* @see #deploy(byte[])
* @return Long
* @see Long deploy(byte[] bytes);
*/
Long deploy(InputStream inputStream);
/**
* 部署流程定义文件同name存在多个版本
* @param bytes
* @return Long
*/
Long deploy(byte[] bytes);
/**
* 部署流程定义文件同name存在多个版本
* @param defineJsonStr
* @see #deploy(byte[])
* @return Long
* @see Long deploy(byte[] bytes);
*/
Long deploy(String defineJsonStr);
/**
* 重新部署定义文件按id更新json
* @param processDefineId
* @param inputStream
* @return Long
*/
void redeploy(Long processDefineId,InputStream inputStream);
/**
* 重新部署定义文件按id更新json
* @param processDefineId
* @param bytes
* @return Long
*/
void redeploy(Long processDefineId,byte[] bytes);
/**
* 重新部署定义文件按id更新json
* @param defineJsonStr
* @return Long
*/
void redeploy(Long processDefineId,String defineJsonStr);
/**
* 卸载流程
* @param processDefineId
*/
void unDeploy(Long processDefineId);
/**
* 回恢流程
* @param processDefineId
*/
void reDeploy(Long processDefineId);
/**
* 只更新type
* @param processDefineId
* @param type
*/
void updateType(Long processDefineId, String type);
/**
* 根据ID获取流程定义对象
* @param processDefineId
* @return
*/
ProcessDefine getById(Long processDefineId);
/**
* 根据id获取流程模型
* @param processDefineId
* @return
*/
ProcessModel getProcessModel(Long processDefineId);
/**
* 流程定义转成流程模型
* @param processDefine
* @return
*/
ProcessModel processDefineToModel(ProcessDefine processDefine);
/**
* 根据id获取定义json字符串
* @param processDefineId
* @return
*/
String getDefineJsonStr(Long processDefineId);
/**
* 获取流程定义json对象
* @param processDefineId
* @return
*/
JSONObject getDefineJsonObject(Long processDefineId);
/**
* 启用/禁用
* @param param
*/
void upAndDown(UpAndDownParam param);
/**
* 根据名称获取最新的流程定义
* @param name
* @return
*/
ProcessDefine getLastByName(String name);
/**
* 通过流程定义名称和版本获取流程定义
* @param name
* @param version
* @return
*/
ProcessDefine getProcessDefineByVersion(String name,Integer version);
}
@@ -0,0 +1,49 @@
package com.budwk.app.flow.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.flow.entity.ProcessDesignHis;
import com.budwk.app.flow.vo.ProcessDesignHisVO;
/**
* <p>
* 流程设计历史 服务类
* </p>
*
* @author mldong
* @since 2023-09-25
*/
public interface ProcessDesignHisService extends BaseService<ProcessDesignHis> {
/**
* 添加流程设计历史
* @param param
* @return
*/
boolean save(ProcessDesignHis param);
/**
* 更新流程设计历史
* @param param
* @return
*/
boolean update(ProcessDesignHis param);
/**
* 自定义分页查询流程设计历史
* @param param
* @return
*/
// CommonPage<ProcessDesignHisVO> page(ProcessDesignHisPageParam param);
/**
* 通过id查询
* @param id
* @return
*/
ProcessDesignHisVO findById(Long id);
/**
* 获取最新的流程设计
* @param processDesignId
* @return
*/
ProcessDesignHis getLatestByProcessDesignId(Long processDesignId);
}
@@ -0,0 +1,58 @@
package com.budwk.app.flow.service;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.flow.entity.ProcessDesign;
import com.budwk.app.flow.vo.ProcessDesignTypeVO;
import java.util.List;
/**
* <p>
* 流程设计 服务类
* </p>
*
* @author mldong
* @since 2023-09-25
*/
public interface ProcessDesignService extends BaseService<ProcessDesign> {
/**
* 添加流程设计
* @param param
* @return
*/
boolean save(ProcessDesign param);
/**
* 更新流程设计
* @param param
* @return
*/
boolean update(ProcessDesign param);
/**
* 更新流程定义信息
* @param jsonObject
* @return
*/
boolean updateDefine(JSONObject jsonObject);
/**
* 流程定义部署
* @param processDesignId
*/
void deploy(Long processDesignId);
/**
* 重新部署流程定义文件覆盖最新版本即不生成新版本
* @param processDesignId
*/
void redeploy(Long processDesignId);
/**
* 按流程分类给流程设计分组
* @return
*/
List<ProcessDesignTypeVO> listByType();
}
@@ -0,0 +1,236 @@
package com.budwk.app.flow.service;
import cn.hutool.core.lang.Dict;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.vo.HighLightVO;
import com.budwk.app.flow.vo.ProcessInstanceVO;
import com.budwk.app.flow.vo.ProcessTaskVO;
import java.util.List;
/**
* <p>
* 流程实例 服务类
* </p>
*
* @author mldong
* @since 2023-09-26
*/
public interface ProcessInstanceService extends BaseService<ProcessInstance> {
/**
* 添加流程实例
*
* @param param
* @return
*/
boolean save(ProcessInstance param);
/**
* 更新流程实例
*
* @param param
* @return
*/
boolean update(ProcessInstance param);
/**
* 自定义分页查询流程实例
* @param param
* @return
*/
// CommonPage<ProcessInstanceVO> page(ProcessInstancePageParam param);
/**
* 通过id查询
*
* @param id
* @return
*/
ProcessInstanceVO findById(Long id);
/**
* 将流程实例修改为已完成
*
* @param processInstanceId
*/
void finishProcessInstance(Long processInstanceId);
/**
* 将流程实例修改为拒绝
*
* @param processInstanceId
*/
void rejectProcessInstance(Long processInstanceId);
/**
* 根据流程操作人员父流程实例ID创建流程实例
*
* @param processDefine 流程定义对象
* @param businessKey 业务KEY
* @param operator 操作人员ID
* @param args 参数列表
* @return ProcessInstance 活动流程实例对象
*/
ProcessInstance createProcessInstance(ProcessDefine processDefine, String businessKey, String operator, Dict args);
/**
* 根据流程操作人员父流程实例ID创建流程实例
*
* @param processDefine 流程定义对象
* @param businessKey 业务KEY
* @param operator 操作人员ID
* @param args 参数列表
* @param parentId
* @param parentNodeName
* @return ProcessInstance 活动流程实例对象
*/
ProcessInstance createProcessInstance(ProcessDefine processDefine, String businessKey, String operator, Dict args, Long parentId, String parentNodeName);
/**
* 向指定实例id添加全局变量数据
*
* @param processDefineId 实例id
* @param args 变量数据
*/
void addVariable(Long processDefineId, Dict args);
/**
* 移除指定实例id中的全局变量
*
* @param processDefineId 实例id
* @param keys 移除变量keys
*/
void removeVariable(Long processDefineId, String... keys);
/**
* 保存流程实例
*
* @param processInstance 流程实例对象
*/
void saveProcessInstance(ProcessInstance processInstance);
/**
* 流程实例强制终止与唤醒相对应
*
* @param processInstanceId 流程实例id
* @param operator 处理人员
*/
void interrupt(Long processInstanceId, String operator);
/**
* 唤醒历史流程实例与终止相对应
*
* @param processInstanceId 流程实例id
* @param operator
*/
void resume(Long processInstanceId, String operator);
/**
* 挂起流程与激活相对应
*
* @param processInstanceId 流程实例id
* @param operator 处理人员
*/
void pending(Long processInstanceId, String operator);
/**
* 激活流程与挂起相对应
*
* @param processInstanceId
* @param operator
*/
void activate(Long processInstanceId, String operator);
/**
* 更新流程实例
*
* @param processInstance 流程实例对象
*/
void updateProcessInstance(ProcessInstance processInstance);
/**
* 根据ID获取流程实例
*
* @param id
* @return
*/
ProcessInstance getById(Long id);
/**
* 启动且执行流程自动执行第一个节点
*
* @param processDefineId
* @param args
*/
Dict startAndExecute(Long processDefineId, Dict args);
/**
* 获取流程实例高亮数据
*
* @param processInstanceId
* @return
*/
HighLightVO highLight(Long processInstanceId);
/**
* 审批记录
*
* @param processInstanceId
* @return
*/
List<ProcessTaskVO> approvalRecord(Long processInstanceId);
/**
* 撤回
*
* @param processInstanceId
* @param operator
*/
void withdraw(Long processInstanceId, String operator);
/**
* 更新会签变量
*
* @param taskModel
* @param execution
* @param taskActors
*/
void updateCountersignVariable(TaskModel taskModel, Execution execution, List<String> taskActors);
/**
* 创建实例抄送
*
* @param processInstanceId
* @param creator
* @param actorIds
*/
void createCCInstance(Long processInstanceId, String creator, String... actorIds);
/**
* 更新抄送状态
*
* @param processInstanceId
* @param actorId
*/
void updateCCStatus(Long processInstanceId, String actorId);
/**
* 自定义分页查询我的抄送
* @param param
* @return
*/
// CommonPage<ProcessInstanceVO> ccInstancePage(ProcessInstancePageParam param);
/**
* 获取流程参与人回显文本数据
*
* @param processInstanceId
* @return
*/
List<LabelValueVO> getAssigneeTextData(Long processInstanceId);
}
@@ -0,0 +1,50 @@
package com.budwk.app.flow.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.flow.entity.ProcessSurrogate;
import com.budwk.app.flow.vo.ProcessSurrogateVO;
/**
* <p>
* 流程委托代理 服务类
* </p>
*
* @author mldong
* @since 2023-12-06
*/
public interface ProcessSurrogateService extends BaseService<ProcessSurrogate> {
/**
* 添加流程委托代理
* @param param
* @return
*/
boolean save(ProcessSurrogate param);
/**
* 更新流程委托代理
* @param param
* @return
*/
boolean update(ProcessSurrogate param);
/**
* 自定义分页查询流程委托代理
* @param param
* @return
*/
// CommonPage<ProcessSurrogateVO> page(ProcessSurrogatePageParam param);
/**
* 通过id查询
* @param id
* @return
*/
ProcessSurrogateVO findById(Long id);
/**
* 获取代理人
* @param operator
* @param processName
* @return
*/
String getSurrogate(String operator,String processName);
}
@@ -0,0 +1,43 @@
package com.budwk.app.flow.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.vo.ProcessTaskActorVO;
/**
* <p>
* 流程任务和参与人关系 服务类
* </p>
*
* @author mldong
* @since 2023-09-26
*/
public interface ProcessTaskActorService extends BaseService<ProcessTaskActor> {
/**
* 添加流程任务和参与人关系
* @param param
* @return
*/
boolean save(ProcessTaskActor param);
/**
* 更新流程任务和参与人关系
* @param param
* @return
*/
boolean update(ProcessTaskActor param);
/**
* 自定义分页查询流程任务和参与人关系
* @param param
* @return
*/
// CommonPage<ProcessTaskActorVO> page(ProcessTaskActorPageParam param);
/**
* 通过id查询
* @param id
* @return
*/
ProcessTaskActorVO findById(Long id);
}
@@ -0,0 +1,194 @@
package com.budwk.app.flow.service;
import cn.hutool.core.lang.Dict;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.model.CustomModel;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.vo.ProcessTaskVO;
import java.util.List;
import java.util.Map;
/**
* <p>
* 流程任务 服务类
* </p>
*
* @author mldong
* @since 2023-09-26
*/
public interface ProcessTaskService extends BaseService<ProcessTask> {
/**
* 添加流程任务
* @param param
* @return
*/
boolean save(ProcessTask param);
/**
* 更新流程任务
* @param param
* @return
*/
boolean update(ProcessTask param);
/**
* 自定义分页查询流程任务
* @param param
* @return
*/
// CommonPage<ProcessTaskVO> page(ProcessTaskPageParam param);
/**
* 通过id查询
* @param id
* @return
*/
ProcessTaskVO findById(Long id);
/**
* 保存流程任务
* @param processTask
*/
void saveProcessTask(ProcessTask processTask);
/**
* 更新流程任务
* @param processTask
*/
void updateProcessTask(ProcessTask processTask);
/**
* 根据流程实例ID获取正在进行的任务
* @param processInstanceId 流程实例ID
* @param taskNames 任务名称
* @return
*/
List<ProcessTask> getDoingTaskList(Long processInstanceId, String[] taskNames);
/**
* 根据流程实例ID获取已完成的任务
* @param processInstanceId 流程实例ID
* @param taskNames 任务名称
* @return
*/
List<ProcessTask> getDoneTaskList(Long processInstanceId, String[] taskNames);
/**
* 将流程任务修改为已完成
* @param processTaskId
* @param operator 任务处理人
* @param args
*/
void finishProcessTask(Long processTaskId, String operator, Dict args);
/**
* 废弃任务
* @param processTaskId
* @param operator
* @param args
*/
void abandonProcessTask(Long processTaskId, String operator, Dict args);
/**
* 根据任务模型和流程执行对象创建任务
* @param taskModel
* @param execution
* @return
*/
List<ProcessTask> createTask(TaskModel taskModel, Execution execution);
/**
* 通过id获取流程任务
* @param id
* @return
*/
ProcessTask getById(Long id);
/**
* 向指定的任务id添加参与者
* @param processTaskId 任务id
* @param actors 参与者
*/
void addTaskActor(Long processTaskId, List<String> actors);
/**
* 向指定的任务id添加参与者
* @param processTaskId 流程任务id
* @param actors 参与者
*/
void addCandidateActor(Long processTaskId, List<String> actors);
/**
* 向指定任务移除参与者
* @param processTaskId 任务id
* @param actors 参与者
*/
void removeTaskActor(Long processTaskId, List<String> actors);
/**
* 根据taskIdoperator判断操作人operator是否允许执行任务
* @param task 任务对象
* @param operator 操作人
* @return boolean 是否允许操作
*/
boolean isAllowed(ProcessTask task, String operator);
/**
* 根据任务ID获取参与者ID
* @param processTaskId
* @return
*/
List<String> getTaskActors(Long processTaskId);
/**
* 查询待办列表
* @param param
* @return
*/
// CommonPage<ProcessTaskVO> todoList(ProcessTaskPageParam param);
/**
* 查询已办列表
* @param param
* @return
*/
// CommonPage<ProcessTaskVO> doneList(ProcessTaskPageParam param);
/**
* 根据当前任务对象驳回至上一步处理
* @param model 流程定义模型用以获取上一步模型对象
* @param currentTask 当前任务对象
* @return Task 任务对象
*/
ProcessTask rejectTask(ProcessModel model, ProcessTask currentTask);
/**
* 获取可跳转的任务节点名称
* @param processInstanceId
* @return
*/
List<LabelValueVO> jumpAbleTaskNameList(Long processInstanceId);
/**
* 分页查询获取候选人
* @param query
* @return
*/
// CommonPage<Map<String,Object>> candidatePage(Dict query);
/**
* 创建会签任务
* @param taskModel
* @param execution
* @return
*/
List<ProcessTask> createCountersignTask(TaskModel taskModel, Execution execution);
/**
* 根据执行对象自定义节点模型创建历史任务记录
* @param execution 执行对象
* @param model 自定义节点模型
* @return 历史任务
*/
ProcessTask history(Execution execution, CustomModel model);
/**
* 根据流程实例ID获取最新的进行中的任务
* @param processInstanceId
* @return
*/
ProcessTaskVO latest(Long processInstanceId);
}
@@ -0,0 +1,242 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.enums.UpAndDownParam;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.parser.ModelParser;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.vo.ProcessDefineVO;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class ProcessDefineServiceImpl extends BaseServiceImpl<ProcessDefine> implements ProcessDefineService {
public ProcessDefineServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean save(ProcessDefine param) {
param.setId(null);
insert(param);
return true;
}
@Override
public boolean update(ProcessDefine param) {
updateIgnoreNull(param);
return true;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public ProcessDefineVO findById(Long id) {
ProcessDefine define = fetch(id);
ProcessDefineVO defineVO = BeanUtil.copyProperties(define, ProcessDefineVO.class);
if (defineVO != null) {
defineVO.setJsonObject(getDefineJsonObject(id));
}
return defineVO;
}
@Override
public Long deploy(InputStream inputStream) {
return deploy(IoUtil.readBytes(inputStream));
}
@Override
public Long deploy(byte[] bytes) {
// Date now = new Date();
// 1. json定义文件转成流程模型
// ProcessModel processModel = ModelParser.parse(bytes);
// 2. 根据名称查询取最新版本的流程定义记录
// List<ProcessDefine> processDefineList = query(Cnd.where(ProcessDefine::getName, "=", processModel.getName()).desc(ProcessDefine::getId));
//
// ProcessDefine processDefine = null;
// // 3.1 如果存在则版本+1并插入一条新的流程定义记录
// if (!processDefineList.isEmpty()) {
// processDefine = processDefineList.get(0);
// processDefine.setId(null);
// processDefine.setVersion(processDefine.getVersion() + 1);
// } else {
// // 3.2 如果不存在则版本默认为1并插入一条新的流程定义记录
// processDefine = new ProcessDefine();
// processDefine.setVersion(0);
// }
// processDefine.setName(processModel.getName());
// processDefine.setDisplayName(processModel.getDisplayName());
// processDefine.setType(processModel.getType());
// processDefine.setState(1);
//// processDefine.setContent(bytes);
// insert(processDefine);
// return processDefine.getId();
return null;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Long deploy(String defineJsonStr) {
Date now = new Date();
// 1. json定义文件转成流程模型
ProcessModel processModel = ModelParser.parse(defineJsonStr);
// 2. 根据名称查询取最新版本的流程定义记录
List<ProcessDefine> processDefineList = query(Cnd.where(ProcessDefine::getName, "=", processModel.getName()).desc(ProcessDefine::getId));
ProcessDefine latestDefine = dao().fetch(ProcessDefine.class, Cnd.where(ProcessDefine::getName, "=", processModel.getName()).desc(ProcessDefine::getId));
ProcessDefine define = null;
if (latestDefine != null) {
define = latestDefine;
define.setId(null);
define.setVersion(latestDefine.getVersion() + 1);
} else {
define = new ProcessDefine();
define.setVersion(1);
}
define.setName(processModel.getName());
define.setDisplayName(processModel.getDisplayName());
define.setCategory(processModel.getCategory());
define.setInstanceUrl(processModel.getInstanceUrl());
define.setH5InstanceUrl(processModel.getH5InstanceUrl());
define.setState(1);
define.setContent(JSONUtil.parseObj(defineJsonStr));
insert(define);
return define.getId();
}
@Override
public void redeploy(Long processDefineId, InputStream inputStream) {
redeploy(processDefineId, IoUtil.readBytes(inputStream));
}
@Override
public void redeploy(Long processDefineId, byte[] bytes) {
Date now = new Date();
// // 1. json定义文件转成流程模型
// ProcessModel processModel = ModelParser.parse(bytes);
// // 2. 构造模型定义信息
// ProcessDefine processDefine = new ProcessDefine();
// processDefine.setId(processDefineId);
// processDefine.setType(processModel.getType());
// processDefine.setName(processModel.getName());
// processDefine.setDisplayName(processModel.getDisplayName());
//// processDefine.setContent(bytes);
// // 3. 更新模型定义文件
// updateIgnoreNull(processDefine);
}
@Override
public void redeploy(Long processDefineId, String defineJsonStr) {
redeploy(processDefineId, defineJsonStr.getBytes());
}
@Override
public void unDeploy(Long processDefineId) {
ProcessDefine processDefine = new ProcessDefine();
processDefine.setId(processDefineId);
processDefine.setState(0);
updateIgnoreNull(processDefine);
}
@Override
public void reDeploy(Long processDefineId) {
ProcessDefine processDefine = new ProcessDefine();
processDefine.setId(processDefineId);
processDefine.setState(1);
updateIgnoreNull(processDefine);
}
@Override
public void updateType(Long processDefineId, String type) {
ProcessDefine processDefine = new ProcessDefine();
processDefine.setId(processDefineId);
processDefine.setType(type);
updateIgnoreNull(processDefine);
}
@Override
public ProcessDefine getById(Long processDefineId) {
return fetch(processDefineId);
}
@Override
public ProcessModel getProcessModel(Long processDefineId) {
return processDefineToModel(getById(processDefineId));
}
@Override
public ProcessModel processDefineToModel(ProcessDefine processDefine) {
if (processDefine == null) return null;
ProcessModel processModel = ModelParser.parse(JSONUtil.toJsonStr(processDefine.getContent()));
// 后续再进行缓存优化减少频繁解析
return processModel;
}
@Override
public String getDefineJsonStr(Long processDefineId) {
ProcessDefine processDefine = getById(processDefineId);
if (processDefine == null) return null;
return JSONUtil.toJsonStr(processDefine.getContent());
}
@Override
public JSONObject getDefineJsonObject(Long processDefineId) {
String s = Convert.toStr(getDefineJsonStr(processDefineId), "{}");
return JSONUtil.parseObj(s);
}
@Override
public void upAndDown(UpAndDownParam param) {
}
@Override
public ProcessDefine getLastByName(String name) {
return fetch(Cnd.where(ProcessDefine::getName, "=", name).and(ProcessDefine::getState, "=", 1).desc(ProcessDefine::getVersion));
}
@Override
public ProcessDefine getProcessDefineByVersion(String name, Integer version) {
Cnd cnd = Cnd.NEW();
cnd.and(ProcessDefine::getName, "=", name);
cnd.andEX(ProcessDefine::getVersion, "=", 1);
cnd.desc(ProcessDefine::getId);
// List<ProcessDefine> list = query(cnd);
ProcessDefine define = fetch(cnd);
return define;
// LambdaQueryWrapper<ProcessDefine> queryWrapper = Wrappers.lambdaQuery(ProcessDefine.class);
// queryWrapper.eq(ProcessDefine::getName,name);
// if(version!=null) {
// queryWrapper.eq(ProcessDefine::getVersion, version);
// }
// queryWrapper.orderByDesc(ProcessDefine::getId);
// List<ProcessDefine> processDefineList = baseMapper.selectList(queryWrapper);
// if(!processDefineList.isEmpty()) {
// return processDefineList.get(0);
// }
// return null;
}
}
@@ -0,0 +1,41 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.entity.ProcessDesignHis;
import com.budwk.app.flow.service.ProcessDesignHisService;
import com.budwk.app.flow.vo.ProcessDesignHisVO;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ProcessDesignHisServiceImpl extends BaseServiceImpl<ProcessDesignHis> implements ProcessDesignHisService {
public ProcessDesignHisServiceImpl(Dao dao) {
super(dao);
}
@Override
public boolean save(ProcessDesignHis param) {
param.setId(null);
insert(param);
return true;
}
@Override
public boolean update(ProcessDesignHis param) {
updateIgnoreNull(param);
return true;
}
@Override
public ProcessDesignHisVO findById(Long id) {
ProcessDesignHis his = fetch(id);
return BeanUtil.copyProperties(his, ProcessDesignHisVO.class);
}
@Override
public ProcessDesignHis getLatestByProcessDesignId(Long processDesignId) {
return fetch(Cnd.where(ProcessDesignHis::getProcessDesignId, "=", processDesignId).desc(ProcessDesignHis::getId));
}
}
@@ -0,0 +1,136 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessDesign;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessDesignService;
import com.budwk.app.flow.vo.ProcessDesignTypeVO;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class ProcessDesignServiceImpl extends BaseServiceImpl<ProcessDesign> implements ProcessDesignService {
public ProcessDesignServiceImpl(Dao dao) {
super(dao);
}
@Inject
private ProcessDefineService processDefineService;
@Override
public boolean save(ProcessDesign param) {
int count = count(Cnd.where(ProcessDesign::getName, "=", param.getName()));
if (count > 0) {
throw new BaseException("编码已存在");
}
insert(param);
return true;
}
@Override
public boolean update(ProcessDesign param) {
int count = count(Cnd.where(ProcessDesign::getId, "!=", param.getId()).and(ProcessDesign::getName, "=", param.getName()));
if (count > 0) {
throw new BaseException("编码已存在");
}
updateIgnoreNull(param);
return true;
}
// @Override
// public ProcessDesignVO findById(Long id) {
// ProcessDesignVO vo = BeanUtil.copyProperties(fetch(id), ProcessDesignVO.class);
// byte[] content = vo.getContent();
// if (content != null) {
// String jsonStr = Convert.toStr(StrUtil.str(content, Charset.defaultCharset()), "{}");
// JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
// vo.setJsonObject(jsonObject);
// }
// JSONObject jsonObject = vo.getJsonObject();
// if (!jsonObject.containsKey(FlowConst.PROCESS_NAME_KEY)) {
// jsonObject.set(FlowConst.PROCESS_NAME_KEY, vo.getName());
// }
// if (!jsonObject.containsKey(FlowConst.PROCESS_DISPLAY_NAME_KEY)) {
// jsonObject.set(FlowConst.PROCESS_DISPLAY_NAME_KEY, vo.getDisplayName());
// }
// if (!jsonObject.containsKey("category")) {
// jsonObject.set("category", vo.getType());
// }
// if (!jsonObject.containsKey("instanceUrl")) {
// jsonObject.set("instanceUrl", vo.getInstanceUrl());
// }
// if (!jsonObject.containsKey("h5InstanceUrl")) {
// jsonObject.set("h5InstanceUrl", vo.getH5InstanceUrl());
// }
// return vo;
// return null;
// }
@Override
public boolean updateDefine(JSONObject jsonObject) {
Long processDesignId = jsonObject.getLong(FlowConst.PROCESS_DESIGN_ID_KEY);
jsonObject.remove(FlowConst.PROCESS_DESIGN_ID_KEY);
String name = jsonObject.getStr(FlowConst.PROCESS_NAME_KEY);
String displayName = jsonObject.getStr(FlowConst.PROCESS_DISPLAY_NAME_KEY);
String type = jsonObject.getStr(FlowConst.PROCESS_TYPE);
String instanceUrl = jsonObject.getStr("instanceUrl");
String h5InstanceUrl = jsonObject.getStr("h5InstanceUrl");
String category = jsonObject.getStr("category");
ProcessDesign processDesign = new ProcessDesign();
processDesign.setId(processDesignId);
processDesign.setName(name);
processDesign.setDisplayName(displayName);
processDesign.setCategory(category);
processDesign.setInstanceUrl(instanceUrl);
processDesign.setH5InstanceUrl(h5InstanceUrl);
processDesign.setIsDeployed(0);
// processDesign.setType(type);
update(processDesign);
return true;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void deploy(Long processDesignId) {
ProcessDesign design = fetch(processDesignId);
design.setIsDeployed(1);
update(design);
// 部署到流程定义
processDefineService.deploy(JSONUtil.toJsonStr(design.getContent()));
}
@Override
public void redeploy(Long processDesignId) {
ProcessDesign design = fetch(processDesignId);
design.setIsDeployed(0);
dao().update(design, "isDeployed");
}
@Override
public List<ProcessDesignTypeVO> listByType() {
return List.of();
}
}
@@ -0,0 +1,534 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.expression.ExpressionUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.enums.YesNoEnum;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.Context;
import com.budwk.app.flow.engine.DecisionHandler;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.handlers.impl.MergeBranchHandler;
import com.budwk.app.flow.engine.model.*;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessCcInstance;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.flow.vo.HighLightVO;
import com.budwk.app.flow.vo.ProcessInstanceVO;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.*;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance> implements ProcessInstanceService {
public ProcessInstanceServiceImpl(Dao dao) {
super(dao);
}
@Inject
private ProcessDefineService processDefineService;
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean save(ProcessInstance param) {
param.setId(null);
insert(param);
return true;
}
@Override
public boolean update(ProcessInstance param) {
updateIgnoreNull(param);
return true;
}
@Override
public ProcessInstanceVO findById(Long id) {
Sql sql = Sqls.create("""
SELECT
pd.NAME,
pd.displayName,
pd.version,
t.*
FROM
wf_process_instance t
LEFT JOIN wf_process_define pd ON t.processDefineId = pd.id
WHERE
t.id = @id
""");
sql.setParam("id", id);
sql.setCallback(Sqls.callback.map());
execute(sql);
NutMap nutMap = (NutMap) sql.getResult();
ProcessInstanceVO instanceVO = new ProcessInstanceVO();
BeanUtil.fillBeanWithMapIgnoreCase(nutMap, instanceVO, true);
instanceVO.setJsonObject(processDefineService.getDefineJsonObject(instanceVO.getProcessDefineId()));
return instanceVO;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void finishProcessInstance(Long processInstanceId) {
ProcessInstance processInstance = new ProcessInstance();
processInstance.setId(processInstanceId);
processInstance.setState(ProcessInstanceStateEnum.FINISHED.getCode());
updateIgnoreNull(processInstance);
}
@Override
public void rejectProcessInstance(Long processInstanceId) {
ProcessInstance processInstance = new ProcessInstance();
processInstance.setId(processInstanceId);
processInstance.setState(ProcessInstanceStateEnum.REJECT.getCode());
updateIgnoreNull(processInstance);
}
@Override
public ProcessInstance createProcessInstance(ProcessDefine processDefine, String businessKey, String operator, Dict args) {
return createProcessInstance(processDefine, businessKey, operator, args, null, null);
}
@Override
public ProcessInstance createProcessInstance(ProcessDefine processDefine, String businessKey, String operator, Dict args, Long parentId, String parentNodeName) {
if (args == null) {
args = Dict.create();
}
// 追加用户信息到参数
FlowUtil.addUserInfoToArgs(operator, args);
// 追加自动构造标题
FlowUtil.addAutoGenTitle(processDefine.getDisplayName(), args);
ProcessInstance processInstance = new ProcessInstance();
processInstance.setParentId(parentId);
processInstance.setParentNodeName(parentNodeName);
processInstance.setProcessDefineId(processDefine.getId());
processInstance.setOperator(operator);
processInstance.setState(ProcessInstanceStateEnum.DOING.getCode());
processInstance.setBusinessNo(businessKey);
processInstance.setVariable(JSONUtil.toJsonStr(args));
ProcessModel processModel = ServiceContext.find(ProcessDefineService.class).processDefineToModel(processDefine);
String expireTime = processModel.getExpireTime();
if (StrUtil.isNotEmpty(expireTime)) {
processInstance.setExpireTime(FlowUtil.processTime(expireTime, args));
}
saveProcessInstance(processInstance);
return processInstance;
}
@Override
public void addVariable(Long processDefineId, Dict args) {
ProcessInstance processInstance = fetch(processDefineId);
Dict newDict = Dict.create();
newDict.putAll(JSONUtil.toBean(processInstance.getVariable(), Dict.class));
newDict.putAll(args);
ProcessInstance up = new ProcessInstance();
up.setId(processDefineId);
up.setVariable(JSONUtil.toJsonStr(newDict));
updateIgnoreNull(up);
}
@Override
public void removeVariable(Long processDefineId, String... keys) {
ProcessInstance processInstance = fetch(processDefineId);
Dict oldDict = JSONUtil.toBean(processInstance.getVariable(), Dict.class);
for (String key : keys) {
oldDict.remove(key);
}
ProcessInstance up = new ProcessInstance();
up.setId(processDefineId);
up.setVariable(JSONUtil.toJsonStr(oldDict));
updateIgnoreNull(up);
}
@Override
public void saveProcessInstance(ProcessInstance processInstance) {
insert(processInstance);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void interrupt(Long processInstanceId, String operator) {
Date now = new Date();
// 1. 将该流程实例产生的任务状态修改为终止
ProcessTask processTask = new ProcessTask();
processTask.setTaskState(ProcessTaskStateEnum.INTERRUPT.getCode());
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()));
// 2. 将该流程实例状态修改为终止
ProcessInstance processInstance = new ProcessInstance();
processInstance.setState(ProcessInstanceStateEnum.INTERRUPT.getCode());
dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
}
@Override
public void resume(Long processInstanceId, String operator) {
Date now = new Date();
ProcessInstance processInstance = fetch(processInstanceId);
// 1. 更新流程实例状态为进行中
processInstance.setState(ProcessInstanceStateEnum.DOING.getCode());
updateIgnoreNull(processInstance);
// 2.被终止的任务状态修改为进行中
ProcessTask processTask = new ProcessTask();
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
}
@Override
public void pending(Long processInstanceId, String operator) {
Date now = new Date();
// 1. 将该流程实例产生的任务状态修改为挂起
ProcessTask processTask = new ProcessTask();
processTask.setTaskState(ProcessTaskStateEnum.PENDING.getCode());
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()));
// 2. 将该流程实例状态修改为挂起
ProcessInstance processInstance = new ProcessInstance();
processInstance.setState(ProcessInstanceStateEnum.PENDING.getCode());
dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
}
@Override
public void activate(Long processInstanceId, String operator) {
ProcessInstance processInstance = fetch(processInstanceId);
// 1. 更新流程实例状态为进行中
processInstance.setState(ProcessInstanceStateEnum.DOING.getCode());
updateIgnoreNull(processInstance);
// 2.被终止的任务状态修改为进行中
ProcessTask processTask = new ProcessTask();
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
}
@Override
public void updateProcessInstance(ProcessInstance processInstance) {
updateIgnoreNull(processInstance);
}
@Override
public ProcessInstance getById(Long id) {
return fetch(id);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Dict startAndExecute(Long processDefineId, Dict args) {
Dict res = Dict.create();
String operator = SecurityUtil.getUserId();
FlowEngine flowEngine = ServiceContext.find(FlowEngine.class);
ProcessInstance processInstance = flowEngine.startProcessInstanceById(processDefineId, null, operator, args);
res.put(FlowConst.PROCESS_INSTANCE_ID_KEY, processInstance.getId());
List<ProcessTask> processTaskList = flowEngine.processTaskService().getDoingTaskList(processInstance.getId(), new String[]{});
// 取任务自动执行
processTaskList.forEach(processTask -> {
args.put(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
flowEngine.executeProcessTask(processTask.getId(), FlowConst.AUTO_ID, args);
});
return res;
}
@Override
public HighLightVO highLight(Long processInstanceId) {
HighLightVO vo = new HighLightVO();
ProcessInstanceVO processInstance = findById(processInstanceId);
FlowEngine flowEngine = ServiceContext.find(FlowEngine.class);
if (processInstance != null) {
ProcessModel processModel = flowEngine.processDefineService().getProcessModel(processInstance.getProcessDefineId());
// 拿到正在进行中的任务==>活跃节点
List<ProcessTask> processTaskList = flowEngine.processTaskService().getDoingTaskList(processInstanceId, new String[]{});
// 拿到历史任务-按更新时间倒序
List<ProcessTask> hisProcessTaskList = flowEngine.processTaskService().query(Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).desc(ProcessTask::getUpdatedAt));
processTaskList.forEach(task -> {
if (!vo.getActiveNodeNames().contains(task.getTaskName())) {
vo.getActiveNodeNames().add(task.getTaskName());
recursionModel(processModel.getStart(), processInstance, processTaskList, hisProcessTaskList, task.getTaskName(), vo);
}
});
// 拿到非正常结束的流程实例状态值
List<Integer> orderStatusList = Arrays.stream(ProcessInstanceStateEnum.values()).filter(item -> !item.equals(ProcessInstanceStateEnum.DOING) && !item.equals(ProcessInstanceStateEnum.FINISHED)).map(ProcessInstanceStateEnum::getCode).toList();
// 非正常结束需要进行特殊处理
if (orderStatusList.contains(processInstance.getState())) {
if (CollectionUtil.isNotEmpty(hisProcessTaskList)) {
ProcessTask lastProcessTask = hisProcessTaskList.get(hisProcessTaskList.size() - 1);
NodeModel nodeModel = processModel.getNode(lastProcessTask.getTaskName());
recursionModel(processModel.getStart(), processInstance, processTaskList, hisProcessTaskList, nodeModel.getOutputs().get(0).getTo(), vo);
}
} else {
List<EndModel> endModels = processModel.getModels(EndModel.class);
if (endModels != null) {
endModels.forEach(endModel -> {
recursionModel(processModel.getStart(), processInstance, processTaskList, hisProcessTaskList, endModel.getName(), vo);
});
}
}
}
return vo;
}
@Override
public List<ProcessTaskVO> approvalRecord(Long processInstanceId) {
List<ProcessTask> processTaskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "not in", List.of(ProcessTaskStateEnum.DOING, ProcessTaskStateEnum.WITHDRAW, ProcessTaskStateEnum.ABANDON))
.asc(ProcessTask::getUpdatedAt));
List<ProcessTaskVO> res = new ArrayList<>();
processTaskList.forEach(processTask -> {
ProcessTaskVO vo = BeanUtil.toBean(processTask, ProcessTaskVO.class);
res.add(vo);
});
return res;
}
@Override
public void withdraw(Long processInstanceId, String operator) {
Date now = new Date();
// 1. 将该流程实例状态修改为撤回
ProcessInstance processInstance = new ProcessInstance();
processInstance.setState(ProcessInstanceStateEnum.WITHDRAW.getCode());
processInstance.setUpdatedAt(System.currentTimeMillis());
processInstance.setUpdatedBy(operator);
int update = dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
if (update >= 1) {
// 2. 将该流程实例产生的任务状态修改为撤回
ProcessTask processTask = new ProcessTask();
processTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
processTask.setUpdatedAt(System.currentTimeMillis());
processTask.setUpdatedBy(operator);
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()));
}
}
/**
* 递归模型处理历史节点与历史边
*
* @param nodeModel 下一个节点
* @param processInstance 历史实例
* @param processTaskList 正在进行的任务
* @param hisProcessTaskList 正在进行的任务
* @param taskName 当前任务节点名称递归停止标识
* @param vo 结果
*/
private void recursionModel(NodeModel nodeModel, ProcessInstance processInstance, List<ProcessTask> processTaskList, List<ProcessTask> hisProcessTaskList, String taskName, HighLightVO vo) {
if (nodeModel.getName().equals(taskName)) {
if (nodeModel instanceof EndModel) {
vo.getHistoryNodeNames().add(nodeModel.getName());
}
return;
}
if (!vo.getHistoryNodeNames().contains(nodeModel.getName())) {
vo.getHistoryNodeNames().add(nodeModel.getName());
nodeModel.getOutputs().stream().filter(output -> {
// 默认取决策节点前面第一个节点为任务节点-待优化
NodeModel defaultDecisionInputModel = null;
ProcessTask historyTask = null;
if (nodeModel instanceof DecisionModel) {
defaultDecisionInputModel = nodeModel.getInputs().get(0).getSource();
NodeModel finalDefaultDecisionInputModel = defaultDecisionInputModel;
historyTask = hisProcessTaskList.stream().filter(hisTask -> {
return finalDefaultDecisionInputModel.getName().equals(hisTask.getTaskName());
}).findFirst().orElse(null);
}
Map<String, Object> args = new HashMap<>();
args.putAll(JSONUtil.parseObj(processInstance.getVariable()));
if (historyTask != null) {
args.putAll(JSONUtil.parseObj(historyTask.getVariable()));
}
if (StrUtil.isNotEmpty(output.getExpr())
&& nodeModel instanceof DecisionModel
&& defaultDecisionInputModel != null) {
return Convert.toBool(ExpressionUtil.eval(output.getExpr(), args));
}
if (nodeModel instanceof DecisionModel) {
String expr = ((DecisionModel) nodeModel).getExpr();
String handleClass = ((DecisionModel) nodeModel).getHandleClass();
if (StrUtil.isNotEmpty(expr)) {
String nextNodeName = Convert.toStr(ExpressionUtil.eval(expr, args));
return output.getTo().equals(nextNodeName);
} else if (StrUtil.isNotEmpty(handleClass)) {
DecisionHandler decisionHandler = ReflectUtil.newInstance(handleClass);
Execution execution = new Execution();
execution.setProcessInstanceId(processInstance.getId());
execution.setProcessInstance(processInstance);
execution.setNodeModel(nodeModel);
execution.setArgs(BeanUtil.toBean(args, Dict.class));
String nextNodeName = decisionHandler.decide(execution);
return output.getTo().equals(nextNodeName);
} else {
return false;
}
}
if (nodeModel instanceof JoinModel) {
// 合并节点
boolean isMerged = MergeBranchHandler.isMerged(processInstance.getId(), nodeModel);
if (!isMerged) {
// 未能合并要把已加入历史的节点和边删除
vo.getHistoryNodeNames().remove(nodeModel.getName());
nodeModel.getInputs().forEach(input -> {
vo.getHistoryEdgeNames().remove(input.getName());
});
}
return isMerged;
}
return true;
}).forEach(transitionModel -> {
if (!vo.getHistoryEdgeNames().contains(transitionModel.getName())) {
vo.getHistoryEdgeNames().add(transitionModel.getName());
recursionModel(transitionModel.getTarget(), processInstance, processTaskList, hisProcessTaskList, taskName, vo);
}
});
}
}
@Override
public void updateCountersignVariable(TaskModel taskModel, Execution execution, List<String> taskActors) {
/**
* nrOfActivateInstances当前活动的实例数量即还没有完成的实例数量
* loopCounter 循环计数器办理人在列表中的索引
* nrOfInstances会签中总共的实例数
* nrOfCompletedInstances已经完成的实例数量
* operatorList会签办理人列表
*/
String taskName = taskModel.getName();
Long processInstanceId = execution.getProcessInstanceId();
// 会签任务变量csv_{taskName}_为前辍
String prefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX + taskName + "_";
Dict addVariable = Dict.create();
// 更新会签总实例数nrOfInstances
addVariable.put(prefix + FlowConst.NR_OF_INSTANCES, taskActors.size());
// 更新会签当前活动实例数nrOfActivateInstances
addVariable.put(prefix + FlowConst.NR_OF_ACTIVATE_INSTANCES, execution.getDoingTaskList().size());
// 更新会签已完的实例数nrOfCompletedInstances
addVariable.put(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES, execution.getArgs().get(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES));
// 更新会签操作人列表countersignOperatorList
addVariable.put(prefix + FlowConst.COUNTERSIGN_OPERATOR_LIST, taskActors);
ProcessInstanceService processInstanceService = SpringUtil.getBean(ProcessInstanceService.class);
processInstanceService.addVariable(processInstanceId, addVariable);
}
@Override
public void createCCInstance(Long processInstanceId, String creator, String... actorIds) {
Date now = new Date();
Arrays.stream(actorIds).forEach(actorId -> {
// long count = processCcInstanceMapper.selectCount(
// Wrappers.lambdaQuery(ProcessCcInstance.class)
// .eq(ProcessCcInstance::getProcessInstanceId,processInstanceId)
// .eq(ProcessCcInstance::getActorId,actorId)
// );
// if(count==0) {
// // 同一条流程实例下同一个人只允许有一条抄送
// ProcessCcInstance processCcInstance = new ProcessCcInstance();
// processCcInstance.setProcessInstanceId(processInstanceId);
// processCcInstance.setActorId(actorId);
// processCcInstance.setState(YesNoEnum.NO.getCode());
// processCcInstance.setCreateUser(creator);
// processCcInstance.setCreateTime(now);
// processCcInstance.setUpdateUser(creator);
// processCcInstance.setUpdateTime(now);
// processCcInstanceMapper.insert(processCcInstance);
// }
});
}
@Override
public void updateCCStatus(Long processInstanceId, String actorId) {
ProcessCcInstance processCcInstance = new ProcessCcInstance();
processCcInstance.setState(1);
// processCcInstance.setUpdateTime(new Date());
// processCcInstance.setUpdateUser(actorId);
// processCcInstanceMapper.update(processCcInstance, Wrappers.<ProcessCcInstance>lambdaQuery()
// .eq(ProcessCcInstance::getProcessInstanceId,processInstanceId)
// .eq(ProcessCcInstance::getActorId,actorId));
}
@Override
public List<LabelValueVO> getAssigneeTextData(Long processInstanceId) {
List<ProcessTask> hisProcessTaskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "not in", List.of(ProcessTaskStateEnum.DOING.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.ABANDON.getCode()))
.asc(ProcessTask::getUpdatedAt));
if (CollectionUtil.isEmpty(hisProcessTaskList)) return Collections.emptyList();
// 历史任务使用 LinkedHashMap 保持顺序并去重
Map<String, Set<String>> hisTaskToUniqueActors = new LinkedHashMap<>();
for (ProcessTask task : hisProcessTaskList) {
String taskName = task.getTaskName();
Dict dict = JSONUtil.toBean(task.getVariable(), Dict.class);
String realName = dict.getStr(FlowConst.USER_REAL_NAME);
// 初始化或获取当前taskName对应的Set
hisTaskToUniqueActors.computeIfAbsent(taskName, k -> new LinkedHashSet<>()).add(realName);
}
// 处理正在进行节点
List<ProcessTask> doingProcessTaskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()));
// 正在进行中的任务使用 LinkedHashMap 保持顺序并去重
Map<String, Set<String>> taskToUniqueActors = new LinkedHashMap<>();
ProcessTaskService processTaskService = ServiceContext.find(ProcessTaskService.class);
// WfUserApi wfUserApi = ServiceContext.find(WfUserApi.class);
for (ProcessTask task : doingProcessTaskList) {
String taskName = task.getTaskName();
List<String> realNameList = new ArrayList<>();
List<String> actorIds = processTaskService.getTaskActors(task.getId());
actorIds.forEach(actorId -> {
// String realName = wfUserApi.getRealName(actorId);
// if(StrUtil.isNotEmpty(realName)) {
// realNameList.add(realName);
// }
});
// 初始化或获取当前taskName对应的Set
taskToUniqueActors.computeIfAbsent(taskName, k -> new LinkedHashSet<>()).add(realNameList.stream().distinct().collect(Collectors.joining(",")));
}
// 追加到his中重复key则覆盖
hisTaskToUniqueActors.putAll(taskToUniqueActors);
// 转换为LabelValueVO列表
List<LabelValueVO> result = hisTaskToUniqueActors.entrySet().stream()
.map(entry -> {
// 使用StrUtil.join来避免空指针异常当集合为空时返回空字符串
String actorsJoined = StrUtil.join(",", entry.getValue());
return LabelValueVO.builder().value(entry.getKey()).label(actorsJoined).build();
})
.collect(Collectors.toList());
return result;
}
}
@@ -0,0 +1,84 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.entity.ProcessSurrogate;
import com.budwk.app.flow.service.ProcessSurrogateService;
import com.budwk.app.flow.vo.ProcessSurrogateVO;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class ProcessSurrogateServiceImpl extends BaseServiceImpl<ProcessSurrogate> implements ProcessSurrogateService {
public ProcessSurrogateServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean save(ProcessSurrogate param) {
param.setId(null);
param.setOperator(SecurityUtil.getUserId());
insert(param);
return true;
}
@Override
public boolean update(ProcessSurrogate param) {
param.setOperator(SecurityUtil.getUserId());
updateIgnoreNull(param);
return true;
}
@Override
public ProcessSurrogateVO findById(Long id) {
ProcessSurrogate surrogate = fetch(id);
return BeanUtil.copyProperties(surrogate, ProcessSurrogateVO.class);
}
@Override
public String getSurrogate(String operator, String processName) {
List<ProcessSurrogate> processSurrogateList = query(Cnd.where(ProcessSurrogate::getOperator, "=", operator)
.and(ProcessSurrogate::getEnabled, "=", 1)
.desc(ProcessSurrogate::getId)
);
for (ProcessSurrogate processSurrogate : processSurrogateList) {
// 为空表示全部优先级最高直接返回
if(StrUtil.isEmpty(processSurrogate.getProcessName())) {
// 开始时间判断
if(processSurrogate.getStartTime()!= null && processSurrogate.getStartTime().getTime() > System.currentTimeMillis()) {
continue;
}
// 结束时间判断
if(processSurrogate.getEndTime()!= null && processSurrogate.getEndTime().getTime() < System.currentTimeMillis()) {
continue;
}
return processSurrogate.getSurrogate();
}
}
// 取满足条件的最新一条
return processSurrogateList.stream().filter(processSurrogate -> {
// 只查询流程名称一样的
return ObjectUtil.equals(processSurrogate.getProcessName(),processName);
}).filter(processSurrogate -> {
// 开始时间判断
if(processSurrogate.getStartTime()!= null && processSurrogate.getStartTime().getTime() > System.currentTimeMillis()) {
return false;
}
// 结束时间判断
if(processSurrogate.getEndTime()!= null && processSurrogate.getEndTime().getTime() < System.currentTimeMillis()) {
return false;
}
return true;
}).map(ProcessSurrogate::getSurrogate).findFirst().orElse(null);
}
}
@@ -0,0 +1,38 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.service.ProcessTaskActorService;
import com.budwk.app.flow.vo.ProcessTaskActorVO;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ProcessTaskActorServiceImpl extends BaseServiceImpl<ProcessTaskActor> implements ProcessTaskActorService {
public ProcessTaskActorServiceImpl(Dao dao) {
super(dao);
}
@Override
public boolean save(ProcessTaskActor param) {
param.setId(null);
insert(param);
return true;
}
@Override
public boolean update(ProcessTaskActor param) {
updateIgnoreNull(param);
return true;
}
@Override
public ProcessTaskActorVO findById(Long id) {
ProcessTaskActor taskActor = fetch(id);
if (taskActor != null) {
return BeanUtil.copyProperties(taskActor, ProcessTaskActorVO.class);
}
return null;
}
}
@@ -0,0 +1,482 @@
package com.budwk.app.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.expression.ExpressionUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.AssignmentHandler;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.engine.model.CustomModel;
import com.budwk.app.flow.engine.model.NodeModel;
import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.enums.*;
import com.budwk.app.flow.service.ProcessDefineService;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> implements ProcessTaskService {
public ProcessTaskServiceImpl(Dao dao) {
super(dao);
}
@Inject
private ProcessDefineService processDefineService;
@Override
public boolean save(ProcessTask param) {
param.setId(null);
insert(param);
return true;
}
@Override
public boolean update(ProcessTask param) {
updateIgnoreNull(param);
return true;
}
@Override
public ProcessTaskVO findById(Long id) {
ProcessTask task = fetch(id);
ProcessTaskVO vo = BeanUtil.copyProperties(task, ProcessTaskVO.class);
if (vo != null) {
// 参与人员
vo.setTaskActorIdList(getTaskActors(vo.getId()));
// 是否可执行
vo.setExecutable(vo.getTaskActorIdList().contains(SecurityUtil.getUserId()));
// 追加节点定义信息
ProcessInstance processInstance = dao().fetch(ProcessInstance.class, task.getProcessInstanceId());
ProcessModel processModel = processDefineService.getProcessModel(processInstance.getProcessDefineId());
TaskModel taskModel = (TaskModel) processModel.getNode(vo.getTaskName());
vo.setTaskModel(taskModel);
}
return vo;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void saveProcessTask(ProcessTask processTask) {
insert(processTask);
}
@Override
public void updateProcessTask(ProcessTask processTask) {
updateIgnoreNull(processTask);
}
@Override
public List<ProcessTask> getDoingTaskList(Long processInstanceId, String[] taskNames) {
Cnd cnd = Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode());
cnd.andEX(ProcessTask::getTaskName, "in", taskNames);
List<ProcessTask> processTaskList = query(cnd);
return processTaskList;
}
@Override
public List<ProcessTask> getDoneTaskList(Long processInstanceId, String[] taskNames) {
Cnd cnd = Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).and(ProcessTask::getTaskState, "!=", ProcessTaskStateEnum.DOING.getCode());
cnd.andEX(ProcessTask::getTaskName, "", taskNames);
List<ProcessTask> processTaskList = query(cnd);
return processTaskList;
}
@Override
public void finishProcessTask(Long processTaskId, String operator, Dict args) {
ProcessTask his = fetch(processTaskId);
ProcessTask processTask = new ProcessTask();
processTask.setId(processTaskId);
processTask.setTaskState(ProcessTaskStateEnum.FINISHED.getCode());
processTask.setUpdatedAt(System.currentTimeMillis());
processTask.setUpdatedBy(operator);
processTask.setOperator(operator);
processTask.setFinishTime(new Date());
Dict newArgs = Dict.create();
newArgs.putAll(FlowUtil.variableToDict(his.getVariable()));
newArgs.putAll(args);
if (FlowConst.AUTO_ID.equalsIgnoreCase(operator)) {
FlowUtil.addUserInfoToArgs(SecurityUtil.getUserId(), newArgs);
} else {
FlowUtil.addUserInfoToArgs(operator, newArgs);
}
processTask.setVariable(JSONUtil.toJsonStr(newArgs));
updateIgnoreNull(processTask);
// 发布流程任务结束事件
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_END).sourceId(processTaskId).build());
}
@Override
public void abandonProcessTask(Long processTaskId, String operator, Dict args) {
ProcessTask his = fetch(processTaskId);
ProcessTask processTask = new ProcessTask();
processTask.setId(processTaskId);
processTask.setTaskState(ProcessTaskStateEnum.ABANDON.getCode());
processTask.setUpdatedAt(System.currentTimeMillis());
processTask.setUpdatedBy(operator);
processTask.setOperator(operator);
processTask.setFinishTime(new Date());
Dict newArgs = Dict.create();
newArgs.putAll(FlowUtil.variableToDict(his.getVariable()));
newArgs.putAll(args);
if (FlowConst.AUTO_ID.equalsIgnoreCase(operator)) {
FlowUtil.addUserInfoToArgs(SecurityUtil.getUserId(), newArgs);
} else {
FlowUtil.addUserInfoToArgs(operator, newArgs);
}
processTask.setVariable(JSONUtil.toJsonStr(newArgs));
updateIgnoreNull(processTask);
// 发布流程任务结束事件
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_END).sourceId(processTaskId).build());
}
@Override
@Aop(TransAop.READ_COMMITTED)
public List<ProcessTask> createTask(TaskModel taskModel, Execution execution) {
List<ProcessTask> processTaskList = new ArrayList<>();
long now = System.currentTimeMillis();
ProcessTask processTask = new ProcessTask();
processTask.setPerformType(ProcessTaskPerformTypeEnum.NORMAL.getCode());
processTask.setTaskName(taskModel.getName());
processTask.setDisplayName(taskModel.getDisplayName());
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
processTask.setTaskType(taskModel.getTaskType().getCode());
processTask.setFormKey(taskModel.getForm());
processTask.setProcessInstanceId(execution.getProcessInstanceId());
execution.getArgs().put(FlowConst.IS_FIRST_TASK_NODE, FlowUtil.isFistTaskName(execution.getProcessModel(), taskModel.getName()));
// 增加是否为第一个任务节点标识
processTask.setVariable(JSONUtil.toJsonStr(execution.getArgs()));
processTask.setCreatedAt(now);
processTask.setUpdatedAt(now);
processTask.setTaskParentId(Convert.toLong(execution.getProcessTaskId(), 0L));
String expireTime = taskModel.getExpireTime();
if (StrUtil.isNotEmpty(expireTime)) {
processTask.setExpireTime(FlowUtil.processTime(expireTime, execution.getArgs()));
}
insert(processTask);
execution.setProcessTask(processTask);
System.out.println("创建任务:" + processTask.getTaskName() + "," + processTask.getDisplayName());
processTaskList.add(processTask);
addTaskActor(processTask.getId(), getTaskActors(taskModel, execution));
return processTaskList;
}
@Override
public ProcessTask getById(Long id) {
return fetch(id);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void addTaskActor(Long processTaskId, List<String> actors) {
if (CollectionUtil.isEmpty(actors)) return;
List<String> dbActors = getTaskActors(processTaskId);
actors.stream().filter(actor -> {
return !dbActors.contains(actor);
}).forEach(actor -> {
ProcessTaskActor processTaskActor = new ProcessTaskActor();
processTaskActor.setProcessTaskId(processTaskId);
processTaskActor.setActorId(actor);
processTaskActor.setCreatedAt(System.currentTimeMillis());
System.out.println(StrUtil.format("给任务:{},添加参与者:{}", processTaskId, actor));
insert(processTaskActor);
});
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void addCandidateActor(Long processTaskId, List<String> actors) {
if (CollectionUtil.isEmpty(actors)) return;
ProcessTask processTask = fetch(processTaskId);
String prefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX + processTask.getTaskName() + "_";
// 主要调整流程变量中的参与者
ProcessInstance processInstance = dao().fetch(ProcessInstance.class, processTask.getProcessInstanceId());
if (processInstance != null) {
Dict args = JSONUtil.toBean(processInstance.getVariable(), Dict.class);
// 会签办理人列表
List<String> operatorList = Convert.toList(String.class, args.get(prefix + FlowConst.COUNTERSIGN_OPERATOR_LIST));
if (args != null) {
operatorList.addAll(actors);
operatorList = operatorList.stream().distinct().collect(Collectors.toList());
// 更新列表
args.put(prefix + FlowConst.COUNTERSIGN_OPERATOR_LIST, operatorList);
// 更新实例数
args.put(prefix + FlowConst.NR_OF_INSTANCES, operatorList.size());
ProcessInstance up = new ProcessInstance();
up.setId(processInstance.getId());
up.setVariable(JSONUtil.toJsonStr(args));
dao().updateIgnoreNull(up);
}
}
Dict taskArgs = JSONUtil.toBean(processTask.getVariable(), Dict.class);
String countersignType = taskArgs.getStr(FlowConst.COUNTERSIGN_VARIABLE_PREFIX + FlowConst.COUNTERSIGN_TYPE);
if (CountersignTypeEnum.PARALLEL.toString().equalsIgnoreCase(countersignType)) {
// 并行会签要直接创建会签任务
actors.forEach(actor -> {
ProcessTask clone = BeanUtil.toBean(processTask, ProcessTask.class);
clone.setId(null);
clone.setCreatedBy(null);
clone.setCreatedAt(null);
clone.setUpdatedAt(null);
clone.setUpdatedBy(null);
dao().insert(clone);
addTaskActor(clone.getId(), CollectionUtil.newArrayList(actor));
});
}
}
@Override
public void removeTaskActor(Long processTaskId, List<String> actors) {
dao().clear(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTaskId).and(ProcessTaskActor::getActorId, "in", actors));
}
@Override
public boolean isAllowed(ProcessTask task, String operator) {
// 执行者为超级管理员或自动执行用户
if (FlowConst.ADMIN_ID.equalsIgnoreCase(operator) || FlowConst.AUTO_ID.equalsIgnoreCase(operator)) {
return true;
}
// 任务操作者==执行者
if (ObjectUtil.equals(task.getOperator(), operator)) {
return true;
}
// 任务参考者==执行者
if (getTaskActors(task.getId()).contains(operator)) {
return true;
}
return false;
}
@Override
public List<String> getTaskActors(Long processTaskId) {
List<ProcessTaskActor> processTaskActorList = dao().query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTaskId));
return processTaskActorList.stream().map(ProcessTaskActor::getActorId).collect(Collectors.toList());
}
@Override
public ProcessTask rejectTask(ProcessModel model, ProcessTask currentTask) {
Long taskParentId = currentTask.getTaskParentId();
if (ObjectUtil.isEmpty(taskParentId) || taskParentId.equals(0L)) {
throw new BaseException("上步任务ID为空,无法驳回至上一步处理");
}
NodeModel current = model.getNode(currentTask.getTaskName());
ProcessTask history = fetch(taskParentId);
NodeModel parent = model.getNode(history.getTaskName());
if (!NodeModel.canRejected(current, parent)) {
throw new BaseException("无法驳回至上一步处理,请确认上步任务并非fork、join、suprocess以及会签任务");
}
ProcessTask task = BeanUtil.toBean(history, ProcessTask.class);
task.setId(null);
task.setTaskState(ProcessTaskStateEnum.DOING.getCode());
task.setCreatedAt(null);
task.setCreatedBy(null);
task.setUpdatedAt(null);
task.setUpdatedBy(null);
task.setFinishTime(null);
String operator = history.getOperator();
Dict hisVariable = FlowUtil.variableToDict(history.getVariable());
if (hisVariable.get(FlowConst.IS_FIRST_TASK_NODE, false)) {
// 第一个节点的操作人从任务变量中获取
operator = hisVariable.getStr(FlowConst.USER_USER_ID);
}
task.setVariable(JSONUtil.toJsonStr(hisVariable));
task.setOperator(operator);
String expireTime = ((TaskModel) current).getExpireTime();
if (StrUtil.isNotEmpty(expireTime)) {
task.setExpireTime(FlowUtil.processTime(expireTime, hisVariable));
}
saveProcessTask(task);
addTaskActor(task.getId(), CollectionUtil.newArrayList(task.getOperator()));
return task;
}
@Override
public List<LabelValueVO> jumpAbleTaskNameList(Long processInstanceId) {
List<String> taskNames = new ArrayList<>();
List<LabelValueVO> res = new ArrayList<>();
List<ProcessTask> processTaskList = getDoneTaskList(processInstanceId, new String[]{});
processTaskList.stream().filter(processTask -> {
// 不能跳转到会签节点
return !ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode().equals(processTask.getPerformType());
}).forEach(processTask -> {
String taskName = processTask.getTaskName();
if (!taskNames.contains(taskName)) {
taskNames.add(taskName);
res.add(LabelValueVO.builder().label(processTask.getDisplayName()).value(taskName).build());
}
});
return res;
}
@Override
public List<ProcessTask> createCountersignTask(TaskModel taskModel, Execution execution) {
List<ProcessTask> processTaskList = new ArrayList<>();
List<String> taskActors = getTaskActors(taskModel, execution);
List<String> createTaskActors = new ArrayList<>();
if (CountersignTypeEnum.PARALLEL.equals(taskModel.getCountersignType())) {
// 并行一个参与者一个任务同时创建
createTaskActors.addAll(taskActors);
// 追加会签类型参数
execution.getArgs().put(FlowConst.COUNTERSIGN_VARIABLE_PREFIX + FlowConst.COUNTERSIGN_TYPE, CountersignTypeEnum.PARALLEL.toString());
} else {
// 串行一个参与者一个任务顺序创建默认只创建第一个其他等执行完一个后再创建
String prefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX + taskModel.getName() + "_";
// 获取循环计数器默认值为-1
int loopCounter = execution.getArgs().get(prefix + FlowConst.LOOP_COUNTER, -1);
if (loopCounter == -1) {
createTaskActors.add(taskActors.get(0));
} else {
createTaskActors.add(taskActors.get(loopCounter + 1));
}
// 追加会签类型参数
execution.getArgs().put(FlowConst.COUNTERSIGN_VARIABLE_PREFIX + FlowConst.COUNTERSIGN_TYPE, CountersignTypeEnum.SEQUENTIAL.toString());
}
createTaskActors.forEach(taskActor -> {
long now = System.currentTimeMillis();
ProcessTask processTask = new ProcessTask();
processTask.setPerformType(ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode());
processTask.setTaskName(taskModel.getName());
processTask.setFormKey(taskModel.getForm());
processTask.setDisplayName(taskModel.getDisplayName());
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
processTask.setTaskType(taskModel.getTaskType().getCode());
processTask.setProcessInstanceId(execution.getProcessInstanceId());
execution.getArgs().put(FlowConst.IS_FIRST_TASK_NODE, FlowUtil.isFistTaskName(execution.getProcessModel(), taskModel.getName()));
processTask.setVariable(JSONUtil.toJsonStr(execution.getArgs()));
processTask.setCreatedAt(now);
processTask.setUpdatedAt(now);
processTask.setTaskParentId(Convert.toLong(execution.getProcessTaskId(), 0L));
String expireTime = taskModel.getExpireTime();
if (StrUtil.isNotEmpty(expireTime)) {
processTask.setExpireTime(FlowUtil.processTime(expireTime, execution.getArgs()));
}
insert(processTask);
execution.setProcessTask(processTask);
System.out.println("创建会签任务:" + processTask.getTaskName() + "," + processTask.getDisplayName());
processTaskList.add(processTask);
addTaskActor(processTask.getId(), CollectionUtil.newArrayList(taskActor));
});
ProcessInstanceService processInstanceService = SpringUtil.getBean(ProcessInstanceService.class);
// 更新会签变量到流程实例参数中
processInstanceService.updateCountersignVariable(taskModel, execution, taskActors);
return processTaskList;
}
@Override
public ProcessTask history(Execution execution, CustomModel model) {
ProcessTask processTask = new ProcessTask();
processTask.setProcessInstanceId(execution.getProcessInstanceId());
long now = System.currentTimeMillis();
processTask.setCreatedAt(now);
processTask.setFinishTime(new Date());
processTask.setDisplayName(model.getDisplayName());
processTask.setTaskName(model.getName());
processTask.setTaskState(ProcessTaskStateEnum.FINISHED.getCode());
processTask.setTaskType(ProcessTaskTypeEnum.RECORD.getCode());
processTask.setTaskParentId(Convert.toLong(execution.getProcessTaskId(), 0L));
processTask.setVariable(JSONUtil.toJsonStr(execution.getArgs()));
insert(processTask);
return processTask;
}
@Override
public ProcessTaskVO latest(Long processInstanceId) {
List<ProcessTask> processTaskList = query(Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode()).desc(ProcessTask::getId));
if (processTaskList.isEmpty()) {
return null;
}
if (processTaskList.size() > 1) {
List<Long> processTaskIds = processTaskList.stream().map(ProcessTask::getId).collect(Collectors.toList());
List<ProcessTaskActor> processTaskActors = dao().query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "in", processTaskIds));
ProcessTaskActor processTaskActor = processTaskActors.stream().filter(item -> {
return ObjectUtil.equals(SecurityUtil.getUserId(), item.getActorId());
}).findAny().orElse(null);
if (processTaskActor != null) {
return findById(processTaskActor.getProcessTaskId());
}
}
return findById(processTaskList.get(0).getId());
}
/**
* 根据Task模型的assigneeassignmentHandler属性以及运行时数据确定参与者
*
* @param model 任务模型
* @param execution 流程执行对象
* @return
*/
private List<String> getTaskActors(TaskModel model, Execution execution) {
Object nextNodeOperator = execution.getArgs().get(FlowConst.NEXT_NODE_OPERATOR);
if (ObjectUtil.isNotEmpty(nextNodeOperator)) {
// 指定下一节点处理人的优先级最高
if (nextNodeOperator instanceof String) {
return CollectionUtil.newArrayList(((String) nextNodeOperator).split(","));
} else if (nextNodeOperator instanceof Collection) {
return CollectionUtil.newArrayList((Collection) nextNodeOperator);
}
}
List<String> res = new ArrayList<>();
Dict args = execution.getArgs();
String assignee = model.getAssignee();
// 参与者属性不为空
if (StrUtil.isNotEmpty(assignee)) {
// 多个使用英文逗号分割
String[] assigneeArr = assignee.split(",");
for (int i = 0; i < assigneeArr.length; i++) {
// // 2025-07-26 增加表达式解析
// if (assigneeArr[i].startsWith("{") && assigneeArr[i].endsWith("}")) {
// Object eval = ExpressionUtil.eval(assigneeArr[i], args);
// // 如果转换失败则使用原值
// res.add(Convert.toStr(eval, assigneeArr[i]));
// }
// 如果args中存在assigneeArr[i]为key的数据==>参与者设置方式中的动态传递
// 如果args中不存在assigneeArr[i]为key的数据==>参与者设置方式中的静态配置
res.add(args.get(assigneeArr[i], assigneeArr[i]));
}
} else {
String assignmentHandler = model.getAssignmentHandler();
if (StrUtil.isNotEmpty(assignmentHandler)) {
AssignmentHandler assignmentHandlerObj = ReflectUtil.newInstance(assignmentHandler);
res.addAll(assignmentHandlerObj.assign(model, execution));
}
}
return res;
}
}

Some files were not shown because too many files have changed in this diff Show More