Compare commits

...

3 Commits

Author SHA1 Message Date
Jerry
f95c7b8695 commit:流程支持自动化任务 2024-10-11 21:43:29 +08:00
Jerry
694e4af055 commit:流程支持自动化任务 2024-10-11 21:43:13 +08:00
Jerry
32c31fd4df commit:流程支持自动化任务 2024-10-11 21:42:49 +08:00
153 changed files with 10269 additions and 393 deletions

View File

@@ -78,6 +78,10 @@ public final class ApplicationConstant {
* 请求头跟踪id名。 * 请求头跟踪id名。
*/ */
public static final String HTTP_HEADER_TRACE_ID = "traceId"; public static final String HTTP_HEADER_TRACE_ID = "traceId";
/**
* 请求头业务流水号id名。
*/
public static final String HTTP_HEADER_TRANS_ID = "transId";
/** /**
* 请求头菜单Id。 * 请求头菜单Id。
*/ */

View File

@@ -434,6 +434,19 @@ public class MyCommonUtil {
return builder.toString(); return builder.toString();
} }
/**
* 获取当前请求的traceId。
*
* @return 当前请求的traceId。
*/
public static String getTraceId() {
HttpServletRequest request = ContextUtil.getHttpRequest();
if (request == null) {
return null;
}
return request.getHeader(ApplicationConstant.HTTP_HEADER_TRACE_ID);
}
/** /**
* 私有构造函数,明确标识该常量类的作用。 * 私有构造函数,明确标识该常量类的作用。
*/ */

View File

@@ -1,11 +1,14 @@
package com.orangeforms.common.dbutil.util; package com.orangeforms.common.dbutil.util;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.constant.FieldFilterType; import com.orangeforms.common.core.constant.FieldFilterType;
import com.orangeforms.common.core.exception.InvalidDblinkTypeException; import com.orangeforms.common.core.exception.InvalidDblinkTypeException;
@@ -28,6 +31,7 @@ import net.sf.jsqlparser.statement.select.SelectItem;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.Serializable;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -48,12 +52,12 @@ public abstract class DataSourceUtil {
private static final Map<Integer, DataSourceProvider> PROVIDER_MAP = new HashMap<>(5); private static final Map<Integer, DataSourceProvider> PROVIDER_MAP = new HashMap<>(5);
protected final Map<Long, DataSourceProvider> dblinkProviderMap = new ConcurrentHashMap<>(4); protected final Map<Long, DataSourceProvider> dblinkProviderMap = new ConcurrentHashMap<>(4);
private static final String SQL_SELECT = " SELECT "; public static final String SQL_SELECT = " SELECT ";
private static final String SQL_SELECT_FROM = " SELECT * FROM ("; public static final String SQL_SELECT_FROM = " SELECT * FROM (";
private static final String SQL_AS_TMP = " ) tmp "; public static final String SQL_AS_TMP = " ) tmp ";
private static final String SQL_ORDER_BY = " ORDER BY "; public static final String SQL_ORDER_BY = " ORDER BY ";
private static final String SQL_AND = " AND "; public static final String SQL_AND = " AND ";
private static final String SQL_WHERE = " WHERE "; public static final String SQL_WHERE = " WHERE ";
private static final String LOG_PREPARING_FORMAT = "==> Preparing: {}"; private static final String LOG_PREPARING_FORMAT = "==> Preparing: {}";
private static final String LOG_PARMS_FORMAT = "==> Parameters: {}"; private static final String LOG_PARMS_FORMAT = "==> Parameters: {}";
private static final String LOG_TOTAL_FORMAT = "<== Total: {}"; private static final String LOG_TOTAL_FORMAT = "<== Total: {}";
@@ -354,6 +358,27 @@ public abstract class DataSourceUtil {
return this.getDataListInternnally(dblinkId, provider, sqlCount, sql, datasetParam, paramList); return this.getDataListInternnally(dblinkId, provider, sqlCount, sql, datasetParam, paramList);
} }
/**
* 执行包含参数变量的增删改操作。
*
* @param connection 数据库链接。
* @param sql SQL语句。
* @param paramList 参数列表。
* @return 影响的行数。
*/
public int execute(Connection connection, String sql, List<Object> paramList) {
try (PreparedStatement stat = connection.prepareStatement(sql)) {
for (int i = 0; i < paramList.size(); i++) {
stat.setObject(i + 1, paramList.get(i));
}
stat.execute();
return stat.getUpdateCount();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
}
}
/** /**
* 在指定数据库链接上执行查询语句,并返回指定映射对象类型的单条数据对象。 * 在指定数据库链接上执行查询语句,并返回指定映射对象类型的单条数据对象。
* *
@@ -414,6 +439,87 @@ public abstract class DataSourceUtil {
} }
} }
/**
* 根据数据库链接的类型将数据表字段类型转换为Java的字段属性类型。
*
* @param column 数据表字段对象。
* @param dblinkType 数据库链接类型。
* @return 返回与Java字段属性的类型名。
*/
public String convertToJavaType(SqlTableColumn column, int dblinkType) {
return this.convertToJavaType(
column.getColumnType(), column.getNumericPrecision(), column.getNumericScale(), dblinkType);
}
/**
* 根据数据库链接的类型将数据表字段类型转换为Java的字段属性类型。
*
* @param columnType 表字段类型。
* @param numericPrecision 数值的精度。
* @param numericScale 数值的刻度。
* @param dblinkType 数据库链接类型。
* @return 返回与Java字段属性的类型名。
*/
public String convertToJavaType(String columnType, Integer numericPrecision, Integer numericScale, int dblinkType) {
DataSourceProvider provider = this.getProvider(dblinkType);
if (provider == null) {
throw new MyRuntimeException("Unsupported Data Type");
}
return provider.convertColumnTypeToJavaType(columnType, numericPrecision, numericScale);
}
/**
* 根据Java字段属性的类型转换参数中的values值到与fieldType匹配的值类型数据列表。
*
* @param fieldType Java字段属性值。
* @param values 字符串类型的参数值列表该参数为JSON数组。
* @return 目标类型的参数值列表。
*/
public List<Serializable> convertToColumnValues(String fieldType, String values) {
List<Serializable> valueList = new LinkedList<>();
if (StrUtil.isBlank(values)) {
return valueList;
}
JSONArray valueArray = JSON.parseArray(values);
for (int i = 0; i < valueArray.size(); i++) {
String v = valueArray.getString(i);
valueList.add(this.convertToColumnValue(fieldType, v));
}
return valueList;
}
/**
* 根据Java字段属性的类型转换参数中的value值到与fieldType匹配的值类型。
*
* @param fieldType Java字段属性值。
* @param value 参数值。
* @return 目标类型的参数值。
*/
public Serializable convertToColumnValue(String fieldType, Serializable value) {
if (value == null) {
return null;
}
switch (fieldType) {
case "Long":
return Convert.toLong(value);
case "Integer":
return Convert.toInt(value);
case "BigDecimal":
return Convert.toBigDecimal(value);
case "Double":
return Convert.toDouble(value);
case "Boolean":
return Convert.toBool(value);
case "Date":
return value;
case "String":
return value.toString();
default:
break;
}
return null;
}
/** /**
* 计算过滤从句和过滤参数。 * 计算过滤从句和过滤参数。
* *

View File

@@ -20,6 +20,11 @@
<artifactId>common-satoken</artifactId> <artifactId>common-satoken</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.orangeforms</groupId>
<artifactId>common-dbutil</artifactId>
<version>1.0.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.orangeforms</groupId> <groupId>com.orangeforms</groupId>
<artifactId>common-datafilter</artifactId> <artifactId>common-datafilter</artifactId>

View File

@@ -96,6 +96,10 @@ public final class FlowApprovalType {
* 空审批人自动退回。 * 空审批人自动退回。
*/ */
public static final String EMPTY_USER_AUTO_REJECT = "empty_user_auto_reject"; public static final String EMPTY_USER_AUTO_REJECT = "empty_user_auto_reject";
/**
* 自动化任务。
*/
public static final String AUTO_FLOW_TASK = "auto_flow_task";
/** /**
* 私有构造函数,明确标识该常量类的作用。 * 私有构造函数,明确标识该常量类的作用。

View File

@@ -0,0 +1,69 @@
package com.orangeforms.common.flow.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 工作流自动化任务的动作类型。
*
* @author Jerry
* @date 2024-07-02
*/
public class FlowAutoActionType {
/**
* 添加新数据。
*/
public static final int ADD_NEW = 0;
/**
* 更新数据。
*/
public static final int UPDATE = 1;
/**
* 删除数据。
*/
public static final int DELETE = 2;
/**
* 查询单条数据。
*/
public static final int SELECT_ONE = 3;
/**
* 聚合计算。
*/
public static final int AGGREGATION_CALC = 5;
/**
* 数值计算。
*/
public static final int NUMBER_CALC = 6;
/**
* HTTP请求调用
*/
public static final int HTTP = 10;
private static final Map<Integer, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(ADD_NEW, "添加新数据");
DICT_MAP.put(UPDATE, "更新数据");
DICT_MAP.put(DELETE, "删除数据");
DICT_MAP.put(SELECT_ONE, "查询单条数据");
DICT_MAP.put(AGGREGATION_CALC, "聚合计算");
DICT_MAP.put(NUMBER_CALC, "数值计算");
DICT_MAP.put(HTTP, "HTTP请求调用");
}
/**
* 根据类型值返回显示值。
*
* @param flowActionType 类型值。
* @return 对应的显示名。
*/
public static String getShowNname(int flowActionType) {
return DICT_MAP.get(flowActionType);
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private FlowAutoActionType() {
}
}

View File

@@ -153,6 +153,11 @@ public class FlowConstant {
*/ */
public static final String GROUP_TYPE_DEPT_POST_LEADER = "DEPT_POST_LEADER"; public static final String GROUP_TYPE_DEPT_POST_LEADER = "DEPT_POST_LEADER";
/**
* 自动执行。
*/
public static final String GROUP_TYPE_AUTO_EXEC = "AUTO_EXEC";
/** /**
* 本部门岗位前缀。 * 本部门岗位前缀。
*/ */
@@ -258,6 +263,11 @@ public class FlowConstant {
*/ */
public static final String EMPTY_USER_TO_ASSIGNEE = "emptyUserToAssignee"; public static final String EMPTY_USER_TO_ASSIGNEE = "emptyUserToAssignee";
/**
* 自动化任务中用于传递的生产者日志对象变量。
*/
public static final String AUTO_FLOW_TRANS_PRODUCER_VAR = "transProducer";
/** /**
* 私有构造函数,明确标识该常量类的作用。 * 私有构造函数,明确标识该常量类的作用。
*/ */

View File

@@ -0,0 +1,276 @@
package com.orangeforms.common.flow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.page.PageMethod;
import com.orangeforms.common.core.annotation.MyRequestBody;
import com.orangeforms.common.core.constant.ErrorCodeEnum;
import com.orangeforms.common.core.object.*;
import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.core.util.MyModelUtil;
import com.orangeforms.common.core.util.MyPageUtil;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.flow.dto.FlowDblinkDto;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.service.FlowDblinkService;
import com.orangeforms.common.flow.util.FlowDataSourceUtil;
import com.orangeforms.common.flow.vo.FlowDblinkVo;
import com.orangeforms.common.log.annotation.OperationLog;
import com.orangeforms.common.log.model.constant.SysOperationLogType;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 工作流数据库链接接口。
*
* @author Jerry
* @date 2024-07-02
*/
@Tag(name = "工作流数据库链接接口")
@Slf4j
@RestController
@RequestMapping("${common-flow.urlPrefix}/flowDblink")
@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true")
public class FlowDblinkController {
@Autowired
private FlowDblinkService flowDblinkService;
@Autowired
private FlowDataSourceUtil dataSourceUtil;
/**
* 新增数据库链接数据。
*
* @param flowDblinkDto 新增对象。
* @return 应答结果对象包含新增对象主键Id。
*/
@SaCheckPermission("flowDblink.all")
@OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/add")
public ResponseResult<Long> add(@MyRequestBody FlowDblinkDto flowDblinkDto) {
String errorMessage = MyCommonUtil.getModelValidationError(flowDblinkDto, false);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
FlowDblink flowDblink = MyModelUtil.copyTo(flowDblinkDto, FlowDblink.class);
flowDblink = flowDblinkService.saveNew(flowDblink);
return ResponseResult.success(flowDblink.getDblinkId());
}
/**
* 更新数据库链接数据。
*
* @param flowDblinkDto 更新对象。
* @return 应答结果对象。
*/
@SaCheckPermission("flowDblink.all")
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/update")
public ResponseResult<Void> update(@MyRequestBody FlowDblinkDto flowDblinkDto) {
String errorMessage = MyCommonUtil.getModelValidationError(flowDblinkDto, true);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
FlowDblink flowDblink = MyModelUtil.copyTo(flowDblinkDto, FlowDblink.class);
ResponseResult<FlowDblink> verifyResult = this.doVerifyAndGet(flowDblinkDto.getDblinkId());
if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult);
}
FlowDblink originalFlowDblink = verifyResult.getData();
if (ObjectUtil.notEqual(flowDblink.getDblinkType(), originalFlowDblink.getDblinkType())) {
errorMessage = "数据验证失败,不能修改数据库类型!";
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
}
String passwdKey = "password";
JSONObject configJson = JSON.parseObject(flowDblink.getConfiguration());
String password = configJson.getString(passwdKey);
if (StrUtil.isNotBlank(password) && StrUtil.isAllCharMatch(password, c -> '*' == c)) {
password = JSON.parseObject(originalFlowDblink.getConfiguration()).getString(passwdKey);
configJson.put(passwdKey, password);
flowDblink.setConfiguration(configJson.toJSONString());
}
if (!flowDblinkService.update(flowDblink, originalFlowDblink)) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success();
}
/**
* 删除数据库链接数据。
*
* @param dblinkId 删除对象主键Id。
* @return 应答结果对象。
*/
@SaCheckPermission("flowDblink.all")
@OperationLog(type = SysOperationLogType.DELETE)
@PostMapping("/delete")
public ResponseResult<Void> delete(@MyRequestBody Long dblinkId) {
String errorMessage;
// 验证关联Id的数据合法性
ResponseResult<FlowDblink> verifyResult = this.doVerifyAndGet(dblinkId);
if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult);
}
if (!flowDblinkService.remove(dblinkId)) {
errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
}
return ResponseResult.success();
}
/**
* 列出符合过滤条件的数据库链接列表。
*
* @param flowDblinkDtoFilter 过滤对象。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 应答结果对象,包含查询结果集。
*/
@SaCheckPermission("flowDblink.all")
@PostMapping("/list")
public ResponseResult<MyPageData<FlowDblinkVo>> list(
@MyRequestBody FlowDblinkDto flowDblinkDtoFilter,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
FlowDblink flowDblinkFilter = MyModelUtil.copyTo(flowDblinkDtoFilter, FlowDblink.class);
String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowDblink.class);
List<FlowDblink> flowDblinkList =
flowDblinkService.getFlowDblinkListWithRelation(flowDblinkFilter, orderBy);
for (FlowDblink dblink : flowDblinkList) {
this.maskOffPassword(dblink);
}
return ResponseResult.success(MyPageUtil.makeResponseData(flowDblinkList, FlowDblinkVo.class));
}
/**
* 查看指定数据库链接对象详情。
*
* @param dblinkId 指定对象主键Id。
* @return 应答结果对象,包含对象详情。
*/
@SaCheckPermission("flowDblink.all")
@GetMapping("/view")
public ResponseResult<FlowDblinkVo> view(@RequestParam Long dblinkId) {
ResponseResult<FlowDblink> verifyResult = this.doVerifyAndGet(dblinkId);
if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult);
}
FlowDblink flowDblink = verifyResult.getData();
flowDblinkService.buildRelationForData(flowDblink, MyRelationParam.full());
if (!StrUtil.equals(flowDblink.getAppCode(), TokenData.takeFromRequest().getAppCode())) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "数据验证失败,当前应用并不存在该数据库链接!");
}
this.maskOffPassword(flowDblink);
return ResponseResult.success(flowDblink, FlowDblinkVo.class);
}
/**
* 获取指定数据库链接下的所有动态表单依赖的数据表列表。
*
* @param dblinkId 数据库链接Id。
* @return 所有动态表单依赖的数据表列表
*/
@SaCheckPermission("flowDblink.all")
@GetMapping("/listDblinkTables")
public ResponseResult<List<SqlTable>> listDblinkTables(@RequestParam Long dblinkId) {
FlowDblink dblink = flowDblinkService.getById(dblinkId);
if (dblink == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success(flowDblinkService.getDblinkTableList(dblink));
}
/**
* 获取指定数据库链接下,指定数据表的所有字段信息。
*
* @param dblinkId 数据库链接Id。
* @param tableName 表名。
* @return 该表的所有字段列表。
*/
@SaCheckPermission("flowDblink.all")
@GetMapping("/listDblinkTableColumns")
public ResponseResult<List<SqlTableColumn>> listDblinkTableColumns(
@RequestParam Long dblinkId, @RequestParam String tableName) {
FlowDblink dblink = flowDblinkService.getById(dblinkId);
if (dblink == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success(flowDblinkService.getDblinkTableColumnList(dblink, tableName));
}
/**
* 测试数据库链接的接口。
*
* @return 应答结果。
*/
@GetMapping("/testConnection")
public ResponseResult<Void> testConnection(@RequestParam Long dblinkId) {
ResponseResult<FlowDblink> verifyAndGet = this.doVerifyAndGet(dblinkId);
if (!verifyAndGet.isSuccess()) {
return ResponseResult.errorFrom(verifyAndGet);
}
try {
dataSourceUtil.testConnection(dblinkId);
return ResponseResult.success();
} catch (Exception e) {
log.error("Failed to test connection with FLOW_DBLINK_ID [" + dblinkId + "]!", e);
return ResponseResult.error(ErrorCodeEnum.DATA_ACCESS_FAILED, "数据库连接失败!");
}
}
/**
* 以字典形式返回全部数据库链接数据集合。字典的键值为[dblinkId, dblinkName]。
* 白名单接口,登录用户均可访问。
*
* @param filter 过滤对象。
* @return 应答结果对象,包含的数据为 List<Map<String, String>>map中包含两条记录key的值分别是id和namevalue对应具体数据。
*/
@GetMapping("/listDict")
public ResponseResult<List<Map<String, Object>>> listDict(@ParameterObject FlowDblinkDto filter) {
List<FlowDblink> resultList =
flowDblinkService.getFlowDblinkList(MyModelUtil.copyTo(filter, FlowDblink.class), null);
return ResponseResult.success(
MyCommonUtil.toDictDataList(resultList, FlowDblink::getDblinkId, FlowDblink::getDblinkName));
}
private ResponseResult<FlowDblink> doVerifyAndGet(Long dblinkId) {
if (MyCommonUtil.existBlankArgument(dblinkId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
FlowDblink flowDblink = flowDblinkService.getById(dblinkId);
if (flowDblink == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
if (!StrUtil.equals(flowDblink.getAppCode(), TokenData.takeFromRequest().getAppCode())) {
return ResponseResult.error(
ErrorCodeEnum.DATA_VALIDATED_FAILED, "数据验证失败,当前应用并不存在该数据库链接!");
}
return ResponseResult.success(flowDblink);
}
private void maskOffPassword(FlowDblink dblink) {
String passwdKey = "password";
JSONObject configJson = JSON.parseObject(dblink.getConfiguration());
if (configJson.containsKey(passwdKey)) {
String password = configJson.getString(passwdKey);
if (StrUtil.isNotBlank(password)) {
configJson.put(passwdKey, StrUtil.repeat('*', password.length()));
dblink.setConfiguration(configJson.toJSONString());
}
}
}
}

View File

@@ -1,6 +1,7 @@
package com.orangeforms.common.flow.controller; package com.orangeforms.common.flow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import com.orangeforms.common.core.exception.MyRuntimeException;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
@@ -35,6 +36,7 @@ import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstance;
@@ -113,6 +115,26 @@ public class FlowOperationController {
return ResponseResult.success(); return ResponseResult.success();
} }
/**
* 发起一个自动化流程实例。
*
* @param processDefinitionKey 流程标识。
* @param variableData 变量数据。
* @return 应答结果对象。
*/
@SaCheckPermission("flowOperation.all")
@OperationLog(type = SysOperationLogType.START_FLOW)
@PostMapping("/startAuto")
public ResponseResult<String> startAuto(
@MyRequestBody(required = true) String processDefinitionKey, @MyRequestBody JSONObject variableData) {
try {
ProcessInstance processInstance = flowApiService.startAuto(processDefinitionKey, variableData);
return ResponseResult.success(processInstance.getProcessInstanceId());
} catch (MyRuntimeException e) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, e.getMessage());
}
}
/** /**
* 获取开始节点之后的第一个任务节点的数据。 * 获取开始节点之后的第一个任务节点的数据。
* *

View File

@@ -0,0 +1,13 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
/**
* 自动化流程变量访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowAutoVariableLogMapper extends BaseDaoMapper<FlowAutoVariableLog> {
}

View File

@@ -0,0 +1,26 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowDblink;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 数据库链接数据操作访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowDblinkMapper extends BaseDaoMapper<FlowDblink> {
/**
* 获取过滤后的对象列表。
*
* @param flowDblinkFilter 主表过滤对象。
* @param orderBy 排序字符串order by从句的参数。
* @return 对象列表。
*/
List<FlowDblink> getFlowDblinkList(
@Param("flowDblinkFilter") FlowDblink flowDblinkFilter, @Param("orderBy") String orderBy);
}

View File

@@ -0,0 +1,13 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowTransProducer;
/**
* 事务性业务数据生产者访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowTransProducerMapper extends BaseDaoMapper<FlowTransProducer> {
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orangeforms.common.flow.dao.FlowAutoVariableLogMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowAutoVariableLog">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="process_instance_id" jdbcType="VARCHAR" property="processInstanceId"/>
<result column="execution_id" jdbcType="VARCHAR" property="executionId"/>
<result column="task_id" jdbcType="VARCHAR" property="taskId"/>
<result column="task_key" jdbcType="VARCHAR" property="taskKey"/>
<result column="trace_id" jdbcType="VARCHAR" property="traceId"/>
<result column="variable_data" jdbcType="LONGVARCHAR" property="variableData"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orangeforms.common.flow.dao.FlowDblinkMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowDblink">
<id column="dblink_id" jdbcType="BIGINT" property="dblinkId"/>
<result column="app_code" jdbcType="VARCHAR" property="appCode"/>
<result column="dblink_name" jdbcType="VARCHAR" property="dblinkName"/>
<result column="dblink_description" jdbcType="VARCHAR" property="dblinkDescription"/>
<result column="configuration" jdbcType="VARCHAR" property="configuration"/>
<result column="dblink_type" jdbcType="INTEGER" property="dblinkType"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="create_user_id" jdbcType="BIGINT" property="createUserId"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="update_user_id" jdbcType="BIGINT" property="updateUserId"/>
</resultMap>
<!-- 如果有逻辑删除字段过滤,请写到这里 -->
<sql id="filterRef">
<!-- 这里必须加上全包名否则当filterRef被其他Mapper.xml包含引用的时候就会调用Mapper.xml中的该SQL片段 -->
<include refid="com.orangeforms.common.flow.dao.FlowDblinkMapper.inputFilterRef"/>
</sql>
<!-- 这里仅包含调用接口输入的主表过滤条件 -->
<sql id="inputFilterRef">
<if test="flowDblinkFilter != null">
<if test="flowDblinkFilter.appCode == null">
AND zz_flow_dblink.app_code IS NULL
</if>
<if test="flowDblinkFilter.appCode != null">
AND zz_flow_dblink.app_code = #{flowDblinkFilter.appCode}
</if>
<if test="flowDblinkFilter.dblinkType != null">
AND zz_flow_dblink.dblink_type = #{flowDblinkFilter.dblinkType}
</if>
</if>
</sql>
<select id="getFlowDblinkList" resultMap="BaseResultMap" parameterType="com.orangeforms.common.flow.model.FlowDblink">
SELECT * FROM zz_flow_dblink
<where>
<include refid="filterRef"/>
</where>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>
</select>
</mapper>

View File

@@ -14,6 +14,7 @@
<result column="bpmn_xml" jdbcType="LONGVARCHAR" property="bpmnXml"/> <result column="bpmn_xml" jdbcType="LONGVARCHAR" property="bpmnXml"/>
<result column="diagram_type" jdbcType="INTEGER" property="diagramType"/> <result column="diagram_type" jdbcType="INTEGER" property="diagramType"/>
<result column="bind_form_type" jdbcType="INTEGER" property="bindFormType"/> <result column="bind_form_type" jdbcType="INTEGER" property="bindFormType"/>
<result column="flow_type" jdbcType="INTEGER" property="flowType"/>
<result column="page_id" jdbcType="BIGINT" property="pageId"/> <result column="page_id" jdbcType="BIGINT" property="pageId"/>
<result column="default_form_id" jdbcType="BIGINT" property="defaultFormId"/> <result column="default_form_id" jdbcType="BIGINT" property="defaultFormId"/>
<result column="default_router_name" jdbcType="VARCHAR" property="defaultRouterName"/> <result column="default_router_name" jdbcType="VARCHAR" property="defaultRouterName"/>
@@ -58,6 +59,9 @@
<if test="flowEntryFilter.status != null"> <if test="flowEntryFilter.status != null">
AND zz_flow_entry.status = #{flowEntryFilter.status} AND zz_flow_entry.status = #{flowEntryFilter.status}
</if> </if>
<if test="flowEntryFilter.flowType != null">
AND zz_flow_entry.flow_type = #{flowEntryFilter.flowType}
</if>
</if> </if>
</sql> </sql>
@@ -73,6 +77,7 @@
status, status,
diagram_type, diagram_type,
bind_form_type, bind_form_type,
flow_type,
page_id, page_id,
default_form_id, default_form_id,
default_router_name, default_router_name,

View File

@@ -14,6 +14,7 @@
<result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/> <result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/>
<result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/> <result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/>
<result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/> <result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/>
<result column="auto_config_json" jdbcType="VARCHAR" property="autoConfigJson"/>
</resultMap> </resultMap>
<insert id="insertList"> <insert id="insertList">
@@ -30,7 +31,8 @@
#{item.deptIds}, #{item.deptIds},
#{item.candidateUsernames}, #{item.candidateUsernames},
#{item.copyListJson}, #{item.copyListJson},
#{item.extraDataJson}) #{item.extraDataJson},
#{item.autoConfigJson})
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orangeforms.common.flow.dao.FlowTransProducerMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowTransProducer">
<id column="trans_id" jdbcType="BIGINT" property="transId"/>
<result column="app_code" jdbcType="VARCHAR" property="appCode"/>
<result column="dblink_id" jdbcType="BIGINT" property="dblinkId"/>
<result column="process_instance_id" jdbcType="VARCHAR" property="processInstanceId"/>
<result column="execution_id" jdbcType="VARCHAR" property="executionId"/>
<result column="task_id" jdbcType="VARCHAR" property="taskId"/>
<result column="task_key" jdbcType="VARCHAR" property="taskKey"/>
<result column="task_name" jdbcType="VARCHAR" property="taskName"/>
<result column="task_comment" jdbcType="VARCHAR" property="taskComment"/>
<result column="url" jdbcType="VARCHAR" property="url"/>
<result column="init_method" jdbcType="VARCHAR" property="initMethod"/>
<result column="trace_id" jdbcType="VARCHAR" property="traceId"/>
<result column="sql_data" jdbcType="LONGVARCHAR" property="sqlData"/>
<result column="auto_task_config" jdbcType="LONGVARCHAR" property="autoTaskConfig"/>
<result column="try_times" jdbcType="INTEGER" property="tryTimes"/>
<result column="error_reason" jdbcType="LONGVARCHAR" property="errorReason"/>
<result column="create_login_name" jdbcType="VARCHAR" property="createUsername"/>
<result column="create_username" jdbcType="VARCHAR" property="createLoginName"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,52 @@
package com.orangeforms.common.flow.dto;
import com.orangeforms.common.core.validator.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 工作流自动化流程数据表所在数据库链接Dto对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Schema(description = "工作流自动化流程数据表所在数据库链接Dto对象")
@Data
public class FlowDblinkDto {
/**
* 主键Id。
*/
@Schema(description = "主键Id")
@NotNull(message = "数据验证失败主键Id不能为空", groups = {UpdateGroup.class})
private Long dblinkId;
/**
* 链接中文名称。
*/
@Schema(description = "链接中文名称")
@NotBlank(message = "数据验证失败,链接中文名称不能为空!")
private String dblinkName;
/**
* 链接描述。
*/
@Schema(description = "链接中文名称")
private String dblinkDescription;
/**
* 配置信息。
*/
@Schema(description = "配置信息")
@NotBlank(message = "数据验证失败,配置信息不能为空!")
private String configuration;
/**
* 数据库链接类型。
*/
@Schema(description = "数据库链接类型")
@NotNull(message = "数据验证失败,数据库链接类型不能为空!")
private Integer dblinkType;
}

View File

@@ -0,0 +1,86 @@
package com.orangeforms.common.flow.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.orangeforms.common.core.util.ApplicationContextHolder;
import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowAutoActionType;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.model.FlowTaskComment;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.object.AutoTaskConfig;
import com.orangeforms.common.flow.service.FlowApiService;
import com.orangeforms.common.flow.service.FlowTaskCommentService;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import com.orangeforms.common.flow.util.AutoFlowHelper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 空审批人审批人检测监听器。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
public class ReceiveTaskEndListener implements ExecutionListener {
private final transient FlowApiService flowApiService =
ApplicationContextHolder.getBean(FlowApiService.class);
private final transient FlowTransProducerService flowTransProducerService =
ApplicationContextHolder.getBean(FlowTransProducerService.class);
private final transient FlowTaskCommentService flowTaskCommentService =
ApplicationContextHolder.getBean(FlowTaskCommentService.class);
private final transient AutoFlowHelper autoFlowHelper =
ApplicationContextHolder.getBean(AutoFlowHelper.class);
@Override
public void notify(DelegateExecution d) {
//先从内存中的临时变量中获取,以便提升正常运行情况下的运行时效率。
FlowTransProducer transProducer =
(FlowTransProducer) d.getTransientVariable(FlowConstant.AUTO_FLOW_TRANS_PRODUCER_VAR);
//如果临时变量中没有存在,则从流程执行实例中查询该变量。
if (transProducer == null) {
transProducer = (FlowTransProducer)
flowApiService.getExecutionVariable(d.getId(), FlowConstant.AUTO_FLOW_TRANS_PRODUCER_VAR);
}
if (transProducer == null) {
FlowTransProducer filter = new FlowTransProducer();
filter.setProcessInstanceId(d.getProcessInstanceId());
filter.setExecutionId(d.getId());
filter.setTaskKey(d.getCurrentActivityId());
List<FlowTransProducer> transProducers = flowTransProducerService.getListByFilter(filter);
if (CollUtil.isNotEmpty(transProducers)) {
transProducers = transProducers.stream()
.sorted(Comparator.comparing(FlowTransProducer::getTransId, Comparator.reverseOrder()))
.collect(Collectors.toList());
transProducer = transProducers.get(0);
}
}
if (transProducer == null) {
return;
}
if (StrUtil.isNotBlank(transProducer.getAutoTaskConfig())) {
AutoTaskConfig taskConfig = JSON.parseObject(transProducer.getAutoTaskConfig(), AutoTaskConfig.class);
autoFlowHelper.executeTask(transProducer.getTransId(), taskConfig, d);
FlowTaskComment comment = new FlowTaskComment();
comment.setTaskKey(d.getCurrentActivityId());
comment.setTaskName(taskConfig.getTaskName());
comment.setProcessInstanceId(d.getProcessInstanceId());
comment.setExecutionId(d.getId());
comment.setApprovalType(FlowApprovalType.AUTO_FLOW_TASK);
String s = StrFormatter.format("执行任务 [{}] 成功,任务类型 [{}]",
taskConfig.getTaskName(), FlowAutoActionType.getShowNname(taskConfig.getActionType()));
comment.setTaskComment(s);
flowTaskCommentService.saveNew(comment);
}
flowTransProducerService.removeById(transProducer.getTransId());
}
}

View File

@@ -0,0 +1,46 @@
package com.orangeforms.common.flow.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.orangeforms.common.core.util.ApplicationContextHolder;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.object.AutoTaskConfig;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import com.orangeforms.common.flow.util.AutoFlowHelper;
import com.orangeforms.common.flow.util.ListenerEventPublishHelper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
/**
* 空审批人审批人检测监听器。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
public class ReceiveTaskStartListener implements ExecutionListener {
private final transient AutoFlowHelper autoFlowHelper =
ApplicationContextHolder.getBean(AutoFlowHelper.class);
private final transient FlowTransProducerService flowTransProducerService =
ApplicationContextHolder.getBean(FlowTransProducerService.class);
private final transient ListenerEventPublishHelper eventPublishHelper =
ApplicationContextHolder.getBean(ListenerEventPublishHelper.class);
@Override
public void notify(DelegateExecution d) {
//TODO 在目标表所在数据库也要创建业务执行的流水表基于TransProduer的TransId做唯一性校验。
AutoTaskConfig taskConfig = autoFlowHelper.parseAutoTaskConfig(d.getProcessDefinitionId(), d.getCurrentActivityId());
FlowTransProducer producerData = new FlowTransProducer();
producerData.setProcessInstanceId(d.getProcessInstanceId());
producerData.setExecutionId(d.getId());
producerData.setTaskKey(d.getCurrentActivityId());
producerData.setDblinkId(taskConfig.getDestDblinkId());
producerData.setInitMethod("ReceiveTaskStartListener.notify");
producerData.setTryTimes(1);
producerData.setAutoTaskConfig(JSON.toJSONString(taskConfig, SerializerFeature.WriteDateUseDateFormat));
flowTransProducerService.saveNew(producerData);
eventPublishHelper.publishEvent(producerData);
}
}

View File

@@ -0,0 +1,65 @@
package com.orangeforms.common.flow.model;
import com.mybatisflex.annotation.*;
import lombok.Data;
import java.util.Date;
/**
* 自动化流程变量实体。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@Table(value = "zz_flow_auto_variable_log")
public class FlowAutoVariableLog {
/**
* 主键Id。
*/
@Id(value = "id")
private Long id;
/**
* 流程实例Id。
*/
@Column(value = "process_instance_id")
private String processInstanceId;
/**
* 执行实例Id。
*/
@Column(value = "execution_id")
private String executionId;
/**
* 任务Id。
*/
@Column(value = "task_id")
private String taskId;
/**
* 任务标识。
*/
@Column(value = "task_key")
private String taskKey;
/**
* 当前请求的traceId。
*/
@Column(value = "trace_id")
private String traceId;
/**
* 变量数据。
*/
@Column(value = "variable_data")
private String variableData;
/**
* 创建时间。
*/
@Column(value = "create_time")
private Date createTime;
}

View File

@@ -0,0 +1,85 @@
package com.orangeforms.common.flow.model;
import com.mybatisflex.annotation.*;
import com.orangeforms.common.core.annotation.RelationConstDict;
import com.orangeforms.common.dbutil.constant.DblinkType;
import lombok.Data;
import java.util.Date;
import java.util.Map;
/**
* 在线表单数据表所在数据库链接实体对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@Table(value = "zz_flow_dblink")
public class FlowDblink {
/**
* 主键Id。
*/
@Id(value = "dblink_id")
private Long dblinkId;
/**
* 应用编码。为空时,表示非第三方应用接入。
*/
@Column(value = "app_code")
private String appCode;
/**
* 链接中文名称。
*/
@Column(value = "dblink_name")
private String dblinkName;
/**
* 链接描述。
*/
@Column(value = "dblink_description")
private String dblinkDescription;
/**
* 配置信息。
*/
private String configuration;
/**
* 数据库链接类型。
*/
@Column(value = "dblink_type")
private Integer dblinkType;
/**
* 创建时间。
*/
@Column(value = "create_time")
private Date createTime;
/**
* 创建者。
*/
@Column(value = "create_user_id")
private Long createUserId;
/**
* 修改时间。
*/
@Column(value = "update_time")
private Date updateTime;
/**
* 更新者。
*/
@Column(value = "update_user_id")
private Long updateUserId;
@RelationConstDict(
masterIdField = "dblinkType",
constantDictClass = DblinkType.class)
@Column(ignore = true)
private Map<String, Object> dblinkTypeDictMap;
}

View File

@@ -88,6 +88,12 @@ public class FlowEntry {
@Column(value = "bind_form_type") @Column(value = "bind_form_type")
private Integer bindFormType; private Integer bindFormType;
/**
* 流程类型。
*/
@Column(value = "flow_type")
private Integer flowType;
/** /**
* 在线表单的页面Id。 * 在线表单的页面Id。
*/ */

View File

@@ -1,6 +1,7 @@
package com.orangeforms.common.flow.model; package com.orangeforms.common.flow.model;
import com.mybatisflex.annotation.*; import com.mybatisflex.annotation.*;
import com.orangeforms.common.flow.model.constant.FlowVariableType;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
@@ -74,4 +75,13 @@ public class FlowEntryVariable {
*/ */
@Column(value = "create_time") @Column(value = "create_time")
private Date createTime; private Date createTime;
public static FlowEntryVariable createSystemVariable(String variableName, String showName) {
FlowEntryVariable variable = new FlowEntryVariable();
variable.variableName = variableName;
variable.showName = showName;
variable.variableType = FlowVariableType.SYSTEM;
variable.builtin = true;
return variable;
}
} }

View File

@@ -84,4 +84,10 @@ public class FlowTaskExt {
*/ */
@Column(value = "extra_data_json") @Column(value = "extra_data_json")
private String extraDataJson; private String extraDataJson;
/**
* 自动化任务配置数据存储为JSON的字符串格式。
*/
@Column(value = "auto_config_json")
private String autoConfigJson;
} }

View File

@@ -0,0 +1,131 @@
package com.orangeforms.common.flow.model;
import com.mybatisflex.annotation.*;
import lombok.Data;
import java.util.Date;
/**
* 流程处理事务事件生产者流水实体。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@Table(value = "zz_flow_trans_producer")
public class FlowTransProducer {
/**
* 流水Id。
*/
@Id(value = "trans_id")
private Long transId;
/**
* 应用编码。为空时,表示非第三方应用接入。
*/
@Column(value = "app_code")
private String appCode;
/**
* 业务数据库链接Id。
*/
@Column(value = "dblink_id")
private Long dblinkId;
/**
* 流程实例Id。
*/
@Column(value = "process_instance_id")
private String processInstanceId;
/**
* 执行实例Id。
*/
@Column(value = "execution_id")
private String executionId;
/**
* 任务Id。
*/
@Column(value = "task_id")
private String taskId;
/**
* 任务标识。
*/
@Column(value = "task_key")
private String taskKey;
/**
* 任务名称。
*/
@Column(value = "task_name")
private String taskName;
/**
* 审批批注。
*/
@Column(value = "task_comment")
private String taskComment;
/**
* 当前请求的url。
*/
@Column(value = "url")
private String url;
/**
* 创建该事务性事件对象的初始方法。格式为:方法名(参数类型1,参数类型2)。
*/
@Column(value = "init_method")
private String initMethod;
/**
* 当前请求的traceId。
*/
@Column(value = "trace_id")
private String traceId;
/**
* 和SQL操作相关的数据。值类型为TransactionalBusinessData.BusinessSqlData对象。
*/
@Column(value = "sql_data")
private String sqlData;
/**
* 自动化流程的任务配置。
*/
@Column(value = "auto_task_config")
private String autoTaskConfig;
/**
* 尝试次数。默认的插入值为1。
*/
@Column(value = "try_times")
private Integer tryTimes;
/**
* 提交业务数据时的错误信息。如果正常提交,该值为空。
*/
@Column(value = "error_reason")
private String errorReason;
/**
* 创建者登录名。
*/
@Column(value = "create_login_name")
private String createLoginName;
/**
* 创建者中文用户名。
*/
@Column(value = "create_username")
private String createUsername;
/**
* 创建时间。
*/
@Column(value = "create_time")
private Date createTime;
}

View File

@@ -0,0 +1,44 @@
package com.orangeforms.common.flow.model.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 流程类型。
*
* @author Jerry
* @date 2024-07-02
*/
public final class FlowEntryType {
/**
* 普通审批。
*/
public static final int NORMAL_TYPE = 0;
/**
* 自动化流程。
*/
public static final int AUTO_TYPE = 1;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(NORMAL_TYPE, "普通审批");
DICT_MAP.put(AUTO_TYPE, "自动化流程");
}
/**
* 判断参数是否为当前常量字典的合法值。
*
* @param value 待验证的参数值。
* @return 合法返回true否则false。
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private FlowEntryType() {
}
}

View File

@@ -19,11 +19,21 @@ public final class FlowVariableType {
* 任务变量。 * 任务变量。
*/ */
public static final int TASK = 1; public static final int TASK = 1;
/**
* 系统内置变量。
*/
public static final int SYSTEM = 2;
/**
* 自定义变量。
*/
public static final int CUSTOM = 4;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2); private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static { static {
DICT_MAP.put(INSTANCE, "流程实例变量"); DICT_MAP.put(INSTANCE, "流程实例变量");
DICT_MAP.put(TASK, "任务变量"); DICT_MAP.put(TASK, "任务变量");
DICT_MAP.put(SYSTEM, "系统内置变量");
DICT_MAP.put(CUSTOM, "自定义变量");
} }
/** /**

View File

@@ -0,0 +1,28 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务的执行数据。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoExecData {
/**
* 数据库链接ID。
*/
private Long dblinkId;
/**
* 执行sql。
*/
private String sql;
/**
* sql参数列表。
*/
private List<Object> params;
}

View File

@@ -0,0 +1,53 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务调用HTTP请求的对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoHttpRequestInfo {
public static final String BODY_TYPE_FORMDATA = "formData";
public static final String BODY_TYPE_RAW = "raw";
public static final String RAW_TYPE_TEXT = "text";
public static final String RAW_TYPE_JSON = "json";
/**
* 请求地址。
*/
private String url;
/**
* POST/GET/PUT ...
*/
private String httpMethod;
/**
* 请求body的类型。
*/
private String bodyType;
/**
* 仅当bodyType为raw的时候可用。
*/
private String rawType;
/**
* 当bodyType为raw的时候请求体的数据。
*/
private String bodyData;
/**
* HTTP请求头列表。
*/
private List<AutoTaskConfig.ValueInfo> headerList;
/**
* 仅当bodyType为formData时可用。
*/
private List<AutoTaskConfig.ValueInfo> formDataList;
/**
* url参数。
*/
private List<AutoTaskConfig.ValueInfo> urlParamList;
}

View File

@@ -0,0 +1,38 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
/**
* 自动化任务调用HTTP的应答对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoHttpResponseData {
public static final String STOP_ON_FAIL = "stop";
public static final String CONTINUE_ON_FAIL = "continue";
/**
* 应答成功的HTTP状态码多个状态码之间逗号分隔。
*/
private String successStatusCode;
/**
* 如果请求状态码为成功,还需要进一步根据应答体中的指定字段,进一步判断是否成功。
* 如data.isSuccess。
*/
private String successBodyField = "success";
/**
* 请求体中错误信息字段。
*/
private String errorMessageBodyField = "errorMessage";
/**
* 失败处理类型。
*/
private String failHandleType;
/**
* HTTP请求的应答体定义。
*/
private String httpResponseBody;
}

View File

@@ -0,0 +1,180 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务的执行配置数据。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoTaskConfig {
public static final String SRC_FILTER_SQL = "sql";
public static final String SRC_FILTER_FIELD = "field";
/**
* 固定值。
*/
public static final int FIXED_VALUE = 0;
/**
* 表字段值。
*/
public static final int COLUMN_VALUE = 1;
/**
* 流程变量。
*/
public static final int VARIABLE_VALUE = 2;
/**
* 任务标识,同时也是当前任务的输出变量名。
*/
private String taskKey;
/**
* 任务名称。
*/
private String taskName;
/**
* 执行动作的类型。
*/
private Integer actionType;
/**
* 数据库链接Id。
*/
private Long srcDblinkId;
/**
* 源数据库类型。
*/
private Integer srcDblinkType;
/**
* 源表名称。
*/
private String srcTableName;
/**
* 源数据的过滤类型可能值为sql/field。
*/
private String srcFilterType;
/**
* 源数据过滤条件列表。
*/
private List<FilterInfo> srcFilterList;
/**
* 源数据过滤sql。
*/
private String srcFilterSql;
/**
* 目标数据库链接Id。
*/
private Long destDblinkId;
/**
* 目标数据库类型。
*/
private Integer destDblinkType;
/**
* 目标表名称。
*/
private String destTableName;
/**
* 目标数据的过滤类型可能值为sql/field。
*/
private String destFilterType;
/**
* 目标数据过滤条件列表。
*/
private List<FilterInfo> destFilterList;
/**
* 目标数据过滤sql。
*/
private String destFilterSql;
/**
* 逻辑删除字段。
*/
private String logicDeleteField;
/**
* 数据插入对象
*/
private List<ValueInfo> insertDataList;
/**
* 数据插入对象
*/
private List<ValueInfo> updateDataList;
/**
* SELECT查询的字段。
*/
private List<String> selectFieldList;
/**
* 聚合数据列表。
*/
private List<AggregationInfo> aggregationDataList;
/**
* 数值计算表达式。
*/
private String calculateFormula;
/**
* HTTP请求信息。
*/
private AutoHttpRequestInfo httpRequestInfo;
/**
* HTTP应答数据。
*/
private AutoHttpResponseData httpResponnseData;
@Data
public static class ValueInfo {
/**
* HTTP任务中使用。
*/
private String key;
/**
* 目标字段名。
*/
private String destColumnName;
/**
* 值的类型,参考当前对象的常量。
*/
private Integer type;
/**
* 值。
*/
private String srcValue;
}
@Data
public static class FilterInfo {
/**
* 过滤字段名。
*/
private String filterColumnName;
/**
* 过滤类型。
*/
private Integer filterType;
/**
* 过滤值类型。
*/
private Integer valueType;
/**
* 过滤值。
*/
private String filterValue;
}
@Data
public static class AggregationInfo {
/**
* 聚合函数如SUM/COUNT/AVERAGE/MIN/MAX。
*/
private String aggregationFunction;
/**
* 聚合字段。
*/
private String aggregationColumn;
/**
* 别名。
*/
private String alias;
}
}

View File

@@ -0,0 +1,43 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务的变量对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoTaskVariable {
/**
* 流程任务定义。
*/
private String taskKey;
/**
* 流程名称。
*/
private String taskName;
/**
* 执行动作的类型。
*/
private Integer actionType;
/**
* 输出变量名。
*/
private String outputVariableName;
/**
* 输出变量的显示名。
*/
private String outputVariableShowName;
/**
* 选择的字段列表。
*/
private List<String> fieldList;
/**
* HTTP请求的应答体定义。
*/
private String httpResponseBody;
}

View File

@@ -22,7 +22,6 @@ import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstance;
import javax.xml.stream.XMLStreamException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -46,6 +45,15 @@ public interface FlowApiService {
*/ */
ProcessInstance start(String processDefinitionId, Object dataId); ProcessInstance start(String processDefinitionId, Object dataId);
/**
* 启动自动化流程实例。
*
* @param processDefinitionKey 流程定义标识。
* @param variableData 变量参数数据。
* @return 新启动的流程实例。
*/
ProcessInstance startAuto(String processDefinitionKey, JSONObject variableData);
/** /**
* 完成第一个用户任务。 * 完成第一个用户任务。
* *
@@ -515,9 +523,8 @@ public interface FlowApiService {
* *
* @param bpmnXml xml格式的流程模型字符串。 * @param bpmnXml xml格式的流程模型字符串。
* @return 转换后的标准的流程模型。 * @return 转换后的标准的流程模型。
* @throws XMLStreamException XML流处理异常
*/ */
BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException; BpmnModel convertToBpmnModel(String bpmnXml);
/** /**
* 回退到上一个用户任务节点。如果没有指定,则回退到上一个任务。 * 回退到上一个用户任务节点。如果没有指定,则回退到上一个任务。
@@ -558,6 +565,14 @@ public interface FlowApiService {
Tuple2<Set<String>, Set<String>> getDeptPostIdAndPostIds( Tuple2<Set<String>, Set<String>> getDeptPostIdAndPostIds(
FlowTaskExt flowTaskExt, String processInstanceId, boolean historic); FlowTaskExt flowTaskExt, String processInstanceId, boolean historic);
/**
* 获取指定流程实例中正在运行是的任务Id列表。
*
* @param processInstanceId 流程实例Id。
* @return 指定流程实例中正在运行是的任务Id列表。
*/
List<String> getCurrentActivityIds(String processInstanceId);
/** /**
* 获取流程图中所有用户任务的映射。 * 获取流程图中所有用户任务的映射。
* *

View File

@@ -0,0 +1,28 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
/**
* 自动化流程变量操作服务接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowAutoVariableLogService extends IBaseService<FlowAutoVariableLog, Long> {
/**
* 保存数据对象。
*
* @param flowAutoVariableLog 数据对象。
*/
void saveNew(FlowAutoVariableLog flowAutoVariableLog);
/**
* 获取指定自动化流程实例的最新变量对象。
*
* @param processInstanceId 流程实例Id。
* @return 自动化流程实例的最新变量对象。
*/
FlowAutoVariableLog getAutoVariableByProcessInstanceId(String processInstanceId);
}

View File

@@ -0,0 +1,89 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.flow.model.FlowDblink;
import java.util.List;
/**
* 数据库链接数据操作服务接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowDblinkService extends IBaseService<FlowDblink, Long> {
/**
* 保存新增对象。
*
* @param flowDblink 新增对象。
* @return 返回新增对象。
*/
FlowDblink saveNew(FlowDblink flowDblink);
/**
* 更新数据对象。
*
* @param flowDblink 更新的对象。
* @param originalFlowDblink 原有数据对象。
* @return 成功返回true否则false。
*/
boolean update(FlowDblink flowDblink, FlowDblink originalFlowDblink);
/**
* 删除指定数据。
*
* @param dblinkId 主键Id。
* @return 成功返回true否则false。
*/
boolean remove(Long dblinkId);
/**
* 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。
* 如果需要同时获取关联数据,请移步(getOnlineDblinkListWithRelation)方法。
*
* @param filter 过滤对象。
* @param orderBy 排序参数。
* @return 查询结果集。
*/
List<FlowDblink> getFlowDblinkList(FlowDblink filter, String orderBy);
/**
* 获取主表的查询结果,以及主表关联的字典数据和一对一从表数据,以及一对一从表的字典数据。
* 该查询会涉及到一对一从表的关联过滤,或一对多从表的嵌套关联过滤,因此性能不如单表过滤。
* 如果仅仅需要获取主表数据,请移步(getOnlineDblinkList),以便获取更好的查询性能。
*
* @param filter 主表过滤对象。
* @param orderBy 排序参数。
* @return 查询结果集。
*/
List<FlowDblink> getFlowDblinkListWithRelation(FlowDblink filter, String orderBy);
/**
* 获取指定DBLink下面的全部数据表。
*
* @param dblink 数据库链接对象。
* @return 全部数据表列表。
*/
List<SqlTable> getDblinkTableList(FlowDblink dblink);
/**
* 获取指定DBLink下指定表名的数据表对象及其关联字段列表。
*
* @param dblink 数据库链接对象。
* @param tableName 数据库中的数据表名。
* @return 数据表对象。
*/
SqlTable getDblinkTable(FlowDblink dblink, String tableName);
/**
* 获取指定DBLink下指定表名的字段列表。
*
* @param dblink 数据库链接对象。
* @param tableName 数据库中的数据表名。
* @return 表的字段列表。
*/
List<SqlTableColumn> getDblinkTableColumnList(FlowDblink dblink, String tableName);
}

View File

@@ -76,6 +76,13 @@ public interface FlowTaskExtService extends IBaseService<FlowTaskExt, String> {
String executionId, String executionId,
FlowTaskExt flowTaskExt); FlowTaskExt flowTaskExt);
/**
* 验证自动化任务的配置如果有问题直接抛出MyRuntimeException异常。
*
* @param taskExt 流程任务的扩展。
*/
void verifyAutoTaskConfig(FlowTaskExt taskExt);
/** /**
* 通过UserTask对象中的扩展节点信息构建FLowTaskExt对象。 * 通过UserTask对象中的扩展节点信息构建FLowTaskExt对象。
* *

View File

@@ -0,0 +1,21 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.flow.model.FlowTransProducer;
/**
* 流程引擎审批操作的生产者流水服务接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowTransProducerService extends IBaseService<FlowTransProducer, Long> {
/**
* 保存新数据。
*
* @param data 数据对象。
* @return 更新后的对象。
*/
FlowTransProducer saveNew(FlowTransProducer data);
}

View File

@@ -19,15 +19,14 @@ import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.core.util.DefaultDataSourceResolver; import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.flow.cmd.AddSequenceMultiInstanceCmd; import com.orangeforms.common.flow.cmd.AddSequenceMultiInstanceCmd;
import com.orangeforms.common.flow.exception.FlowOperationException; import com.orangeforms.common.flow.exception.FlowOperationException;
import com.orangeforms.common.flow.model.constant.FlowEntryType;
import com.orangeforms.common.flow.object.*; import com.orangeforms.common.flow.object.*;
import com.orangeforms.common.flow.constant.FlowConstant; import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.constant.FlowApprovalType; import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowTaskStatus; import com.orangeforms.common.flow.constant.FlowTaskStatus;
import com.orangeforms.common.flow.model.*; import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.service.*; import com.orangeforms.common.flow.service.*;
import com.orangeforms.common.flow.util.BaseFlowIdentityExtHelper; import com.orangeforms.common.flow.util.*;
import com.orangeforms.common.flow.util.CustomChangeActivityStateBuilderImpl;
import com.orangeforms.common.flow.util.FlowCustomExtFactory;
import com.orangeforms.common.flow.vo.FlowTaskVo; import com.orangeforms.common.flow.vo.FlowTaskVo;
import com.orangeforms.common.flow.vo.FlowUserInfoVo; import com.orangeforms.common.flow.vo.FlowUserInfoVo;
import lombok.Cleanup; import lombok.Cleanup;
@@ -107,6 +106,14 @@ public class FlowApiServiceImpl implements FlowApiService {
private FlowCustomExtFactory flowCustomExtFactory; private FlowCustomExtFactory flowCustomExtFactory;
@Autowired @Autowired
private FlowMultiInstanceTransService flowMultiInstanceTransService; private FlowMultiInstanceTransService flowMultiInstanceTransService;
@Autowired
private FlowTransProducerService flowTransProducerService;
@Autowired
private FlowAutoVariableLogService flowAutoVariableLogService;
@Autowired
private FlowOperationHelper flowOperationHelper;
@Autowired
private AutoFlowHelper autoFlowHelper;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
@@ -127,6 +134,45 @@ public class FlowApiServiceImpl implements FlowApiService {
return builder.start(); return builder.start();
} }
@Transactional(rollbackFor = Exception.class)
@Override
public ProcessInstance startAuto(String processDefinitionKey, JSONObject variableData) {
ResponseResult<FlowEntry> flowEntryResult = flowOperationHelper.verifyAndGetFlowEntry(processDefinitionKey);
if (!flowEntryResult.isSuccess()) {
throw new MyRuntimeException(flowEntryResult.getErrorMessage());
}
FlowEntry flowEntry = flowEntryResult.getData();
if (!flowEntry.getFlowType().equals(FlowEntryType.AUTO_TYPE)) {
throw new MyRuntimeException("数据验证失败,该接口只能启动自动化流程!");
}
JSONObject systemVariables = autoFlowHelper.getNonRealtimeSystemVariables();
if (variableData == null) {
variableData = new JSONObject();
}
variableData.putAll(systemVariables);
AutoFlowHelper.setStartAutoInitVariables(variableData);
TokenData tokenData = TokenData.takeFromRequest();
Authentication.setAuthenticatedUserId(tokenData.getLoginName());
String businessKey = variableData.getString("businessKey");
String processDefinitionId = flowEntry.getMainFlowEntryPublish().getProcessDefinitionId();
ProcessInstanceBuilder builder =
runtimeService.createProcessInstanceBuilder()
.processDefinitionId(processDefinitionId).businessKey(businessKey);
if (tokenData.getTenantId() != null) {
builder.tenantId(tokenData.getTenantId().toString());
} else {
if (tokenData.getAppCode() != null) {
builder.tenantId(tokenData.getAppCode());
}
}
ProcessInstance instance = builder.start();
FlowAutoVariableLog data = new FlowAutoVariableLog();
data.setProcessInstanceId(instance.getProcessInstanceId());
data.setVariableData(variableData.toJSONString());
flowAutoVariableLogService.saveNew(data);
return instance;
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public Task takeFirstTask(String processInstanceId, FlowTaskComment flowTaskComment, JSONObject taskVariableData) { public Task takeFirstTask(String processInstanceId, FlowTaskComment flowTaskComment, JSONObject taskVariableData) {
@@ -976,6 +1022,12 @@ public class FlowApiServiceImpl implements FlowApiService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public CallResult stopProcessInstance(String processInstanceId, String stopReason, int status) { public CallResult stopProcessInstance(String processInstanceId, String stopReason, int status) {
ProcessInstance instance = this.getProcessInstance(processInstanceId);
FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(instance.getProcessDefinitionKey());
if (flowEntry.getFlowType().equals(FlowEntryType.AUTO_TYPE)) {
runtimeService.deleteProcessInstance(processInstanceId, stopReason);
return CallResult.ok();
}
List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list(); List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list();
if (CollUtil.isEmpty(taskList)) { if (CollUtil.isEmpty(taskList)) {
return CallResult.error("数据验证失败,当前流程尚未开始或已经结束!"); return CallResult.error("数据验证失败,当前流程尚未开始或已经结束!");
@@ -1085,11 +1137,15 @@ public class FlowApiServiceImpl implements FlowApiService {
} }
@Override @Override
public BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException { public BpmnModel convertToBpmnModel(String bpmnXml) {
BpmnXMLConverter converter = new BpmnXMLConverter(); try {
InputStream in = new ByteArrayInputStream(bpmnXml.getBytes(StandardCharsets.UTF_8)); BpmnXMLConverter converter = new BpmnXMLConverter();
@Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(in); InputStream in = new ByteArrayInputStream(bpmnXml.getBytes(StandardCharsets.UTF_8));
return converter.convertToBpmnModel(reader); @Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(in);
return converter.convertToBpmnModel(reader);
} catch (XMLStreamException e) {
throw new MyRuntimeException(e);
}
} }
@Transactional @Transactional
@@ -1715,6 +1771,15 @@ public class FlowApiServiceImpl implements FlowApiService {
return new Tuple2<>(deptPostIdSet, postIdSet); return new Tuple2<>(deptPostIdSet, postIdSet);
} }
@Override
public List<String> getCurrentActivityIds(String processInstanceId) {
List<Execution> runExecutionList =
runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list();
return runExecutionList.stream()
.map(Execution::getActivityId)
.filter(StrUtil::isNotBlank).collect(Collectors.toList());
}
@Override @Override
public Map<String, UserTask> getAllUserTaskMap(String processDefinitionId) { public Map<String, UserTask> getAllUserTaskMap(String processDefinitionId) {
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

View File

@@ -0,0 +1,53 @@
package com.orangeforms.common.flow.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.flow.dao.FlowAutoVariableLogMapper;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
import com.orangeforms.common.flow.service.FlowAutoVariableLogService;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("flowAutoVariableLogService")
public class FlowAutoVariableLogServiceImpl extends BaseService<FlowAutoVariableLog, Long> implements FlowAutoVariableLogService {
@Autowired
private FlowAutoVariableLogMapper flowAutoVariableLogMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Override
protected BaseDaoMapper<FlowAutoVariableLog> mapper() {
return flowAutoVariableLogMapper;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveNew(FlowAutoVariableLog o) {
o.setId(idGenerator.nextLongId());
o.setTraceId(MyCommonUtil.getTraceId());
o.setCreateTime(new Date());
flowAutoVariableLogMapper.insert(o);
}
@Override
public FlowAutoVariableLog getAutoVariableByProcessInstanceId(String processInstanceId) {
QueryWrapper qw = new QueryWrapper();
qw.eq(FlowAutoVariableLog::getProcessInstanceId, processInstanceId);
return flowAutoVariableLogMapper.selectOneByQuery(qw);
}
}

View File

@@ -0,0 +1,139 @@
package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.Page;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.object.MyRelationParam;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.flow.dao.FlowDblinkMapper;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.service.FlowDblinkService;
import com.orangeforms.common.flow.util.FlowDataSourceUtil;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("flowDblinkService")
public class FlowDblinkServiceImpl extends BaseService<FlowDblink, Long> implements FlowDblinkService {
@Autowired
private FlowDblinkMapper flowDblinkMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private FlowDataSourceUtil dataSourceUtil;
/**
* 返回当前Service的主表Mapper对象。
*
* @return 主表Mapper对象。
*/
@Override
protected BaseDaoMapper<FlowDblink> mapper() {
return flowDblinkMapper;
}
@Transactional(rollbackFor = Exception.class)
@Override
public FlowDblink saveNew(FlowDblink flowDblink) {
flowDblinkMapper.insert(this.buildDefaultValue(flowDblink));
return flowDblink;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean update(FlowDblink flowDblink, FlowDblink originalFlowDblink) {
if (!StrUtil.equals(flowDblink.getConfiguration(), originalFlowDblink.getConfiguration())) {
dataSourceUtil.removeDataSource(flowDblink.getDblinkId());
}
flowDblink.setAppCode(TokenData.takeFromRequest().getAppCode());
flowDblink.setCreateUserId(originalFlowDblink.getCreateUserId());
flowDblink.setUpdateUserId(TokenData.takeFromRequest().getUserId());
flowDblink.setCreateTime(originalFlowDblink.getCreateTime());
flowDblink.setUpdateTime(new Date());
return flowDblinkMapper.update(flowDblink, false) == 1;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean remove(Long dblinkId) {
dataSourceUtil.removeDataSource(dblinkId);
return flowDblinkMapper.deleteById(dblinkId) == 1;
}
@Override
public List<FlowDblink> getFlowDblinkList(FlowDblink filter, String orderBy) {
if (filter == null) {
filter = new FlowDblink();
}
filter.setAppCode(TokenData.takeFromRequest().getAppCode());
return flowDblinkMapper.getFlowDblinkList(filter, orderBy);
}
@Override
public List<FlowDblink> getFlowDblinkListWithRelation(FlowDblink filter, String orderBy) {
List<FlowDblink> resultList = this.getFlowDblinkList(filter, orderBy);
// 在缺省生成的代码中如果查询结果resultList不是Page对象说明没有分页那么就很可能是数据导出接口调用了当前方法。
// 为了避免一次性的大量数据关联,规避因此而造成的系统运行性能冲击,这里手动进行了分批次读取,开发者可按需修改该值。
int batchSize = resultList instanceof Page ? 0 : 1000;
this.buildRelationForDataList(resultList, MyRelationParam.normal(), batchSize);
return resultList;
}
@Override
public List<SqlTable> getDblinkTableList(FlowDblink dblink) {
List<SqlTable> resultList = dataSourceUtil.getTableList(dblink.getDblinkId(), null);
resultList.forEach(t -> t.setDblinkId(dblink.getDblinkId()));
return resultList;
}
@Override
public SqlTable getDblinkTable(FlowDblink dblink, String tableName) {
if (dblink.getDblinkId() == null || StrUtil.isBlank(tableName)) {
return null;
}
SqlTable sqlTable = dataSourceUtil.getTable(dblink.getDblinkId(), tableName);
sqlTable.setDblinkId(dblink.getDblinkId());
sqlTable.setColumnList(getDblinkTableColumnList(dblink, tableName));
return sqlTable;
}
@Override
public List<SqlTableColumn> getDblinkTableColumnList(FlowDblink dblink, String tableName) {
List<SqlTableColumn> columnList = dataSourceUtil.getTableColumnList(dblink.getDblinkId(), tableName);
columnList.forEach(c -> this.makeupSqlTableColumn(c, dblink.getDblinkType()));
return columnList;
}
private void makeupSqlTableColumn(SqlTableColumn sqlTableColumn, int dblinkType) {
sqlTableColumn.setDblinkType(dblinkType);
sqlTableColumn.setAutoIncrement("auto_increment".equals(sqlTableColumn.getExtra()));
}
private FlowDblink buildDefaultValue(FlowDblink flowDblink) {
flowDblink.setDblinkId(idGenerator.nextLongId());
TokenData tokenData = TokenData.takeFromRequest();
flowDblink.setCreateUserId(tokenData.getUserId());
flowDblink.setUpdateUserId(tokenData.getUserId());
Date now = new Date();
flowDblink.setCreateTime(now);
flowDblink.setUpdateTime(now);
flowDblink.setAppCode(tokenData.getAppCode());
return flowDblink;
}
}

View File

@@ -16,6 +16,7 @@ import com.orangeforms.common.core.object.MyRelationParam;
import com.orangeforms.common.core.object.TokenData; import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.util.MyModelUtil; import com.orangeforms.common.core.util.MyModelUtil;
import com.orangeforms.common.core.util.DefaultDataSourceResolver; import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.flow.model.constant.FlowEntryType;
import com.orangeforms.common.redis.util.CommonRedisUtil; import com.orangeforms.common.redis.util.CommonRedisUtil;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper; import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import com.orangeforms.common.flow.listener.*; import com.orangeforms.common.flow.listener.*;
@@ -29,9 +30,7 @@ import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.service.*; import com.orangeforms.common.flow.service.*;
import com.orangeforms.common.flow.model.constant.FlowEntryStatus; import com.orangeforms.common.flow.model.constant.FlowEntryStatus;
import com.orangeforms.common.flow.model.constant.FlowVariableType; import com.orangeforms.common.flow.model.constant.FlowVariableType;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService; import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Deployment;
@@ -40,12 +39,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -111,19 +104,16 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void publish(FlowEntry flowEntry, String initTaskInfo) throws XMLStreamException { public void publish(FlowEntry flowEntry, String initTaskInfo) {
commonRedisUtil.evictFormCache( commonRedisUtil.evictFormCache(FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey()));
FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey()));
FlowCategory flowCategory = flowCategoryService.getById(flowEntry.getCategoryId()); FlowCategory flowCategory = flowCategoryService.getById(flowEntry.getCategoryId());
InputStream xmlStream = new ByteArrayInputStream( BpmnModel bpmnModel = flowApiService.convertToBpmnModel(flowEntry.getBpmnXml());
flowEntry.getBpmnXml().getBytes(StandardCharsets.UTF_8));
@Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlStream);
BpmnXMLConverter converter = new BpmnXMLConverter();
BpmnModel bpmnModel = converter.convertToBpmnModel(reader);
bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName()); bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName());
bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey()); bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey());
this.processAutomaticTask(flowEntry, bpmnModel);
flowApiService.addProcessInstanceEndListener(bpmnModel, FlowFinishedListener.class); flowApiService.addProcessInstanceEndListener(bpmnModel, FlowFinishedListener.class);
List<FlowTaskExt> flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel); List<FlowTaskExt> flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel);
flowTaskExtList.forEach(t -> flowTaskExtService.verifyAutoTaskConfig(t));
if (StrUtil.isNotBlank(flowEntry.getExtensionData())) { if (StrUtil.isNotBlank(flowEntry.getExtensionData())) {
FlowEntryExtensionData flowEntryExtensionData = FlowEntryExtensionData flowEntryExtensionData =
JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class); JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class);
@@ -338,6 +328,18 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
return CallResult.ok(); return CallResult.ok();
} }
private void processAutomaticTask(FlowEntry flowEntry, BpmnModel bpmnModel) {
if (flowEntry.getFlowType().equals(FlowEntryType.NORMAL_TYPE)) {
return;
}
List<ReceiveTask> receiveTasks = bpmnModel.getMainProcess().getFlowElements()
.stream().filter(ReceiveTask.class::isInstance).map(ReceiveTask.class::cast).toList();
receiveTasks.forEach(r -> {
flowApiService.addExecutionListener(r, ReceiveTaskStartListener.class, "start", null);
flowApiService.addExecutionListener(r, ReceiveTaskEndListener.class, "end", null);
});
}
private void insertBuiltinEntryVariables(Long entryId) { private void insertBuiltinEntryVariables(Long entryId) {
Date now = new Date(); Date now = new Date();
FlowEntryVariable operationTypeVariable = new FlowEntryVariable(); FlowEntryVariable operationTypeVariable = new FlowEntryVariable();
@@ -442,6 +444,9 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
.filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c)); .filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c));
BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper();
for (FlowTaskExt t : flowTaskExtList) { for (FlowTaskExt t : flowTaskExtList) {
if (!elementMap.containsKey(t.getTaskId())) {
continue;
}
UserTask userTask = (UserTask) elementMap.get(t.getTaskId()); UserTask userTask = (UserTask) elementMap.get(t.getTaskId());
flowApiService.addTaskCreateListener(userTask, FlowUserTaskListener.class); flowApiService.addTaskCreateListener(userTask, FlowUserTaskListener.class);
Map<String, List<ExtensionAttribute>> attributes = userTask.getAttributes(); Map<String, List<ExtensionAttribute>> attributes = userTask.getAttributes();

View File

@@ -2,6 +2,7 @@ package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
@@ -15,7 +16,9 @@ import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.core.object.Tuple2; import com.orangeforms.common.core.object.Tuple2;
import com.orangeforms.common.core.util.DefaultDataSourceResolver; import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.flow.constant.FlowApprovalType; import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowAutoActionType;
import com.orangeforms.common.flow.constant.FlowConstant; import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.object.AutoTaskConfig;
import com.orangeforms.common.flow.object.FlowElementExtProperty; import com.orangeforms.common.flow.object.FlowElementExtProperty;
import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign; import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign;
import com.orangeforms.common.flow.object.FlowUserTaskExtData; import com.orangeforms.common.flow.object.FlowUserTaskExtData;
@@ -221,16 +224,29 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
return resultUserMapList; return resultUserMapList;
} }
private void buildUserMapList( @Override
List<FlowUserInfoVo> userInfoList, Set<String> loginNameSet, List<FlowUserInfoVo> userMapList) { public void verifyAutoTaskConfig(FlowTaskExt taskExt) {
if (CollUtil.isEmpty(userInfoList)) { if (StrUtil.isBlank(taskExt.getAutoConfigJson())) {
return; return;
} }
for (FlowUserInfoVo userInfo : userInfoList) { AutoTaskConfig taskConfig = JSON.parseObject(taskExt.getAutoConfigJson(), AutoTaskConfig.class);
if (!loginNameSet.contains(userInfo.getLoginName())) { this.verifyDataOperationTask(taskConfig);
loginNameSet.add(userInfo.getLoginName()); String errorMessage;
userMapList.add(userInfo); if (taskConfig.getActionType().equals(FlowAutoActionType.SELECT_ONE)) {
this.verifySrcTableNameEmpty(taskConfig);
} else if (taskConfig.getActionType().equals(FlowAutoActionType.AGGREGATION_CALC)) {
this.verifySrcTableNameEmpty(taskConfig);
if (CollUtil.isEmpty(taskConfig.getAggregationDataList())) {
errorMessage = StrFormatter.format("任务 [{}] 的聚合计算配置不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
} }
} else if (taskConfig.getActionType().equals(FlowAutoActionType.NUMBER_CALC)) {
if (StrUtil.isBlank(taskConfig.getCalculateFormula())) {
errorMessage = StrFormatter.format("任务 [{}] 的数值计算公式不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
} else if (taskConfig.getActionType().equals(FlowAutoActionType.HTTP)) {
this.verifyHttpConfig(taskConfig);
} }
} }
@@ -372,10 +388,93 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
return propertiesData; return propertiesData;
} }
private void verifyDataOperationTask(AutoTaskConfig taskConfig) {
String errorMessage;
if (taskConfig.getActionType().equals(FlowAutoActionType.ADD_NEW)) {
this.verifyDestTableNameEmpty(taskConfig);
if (CollUtil.isEmpty(taskConfig.getInsertDataList())) {
errorMessage = StrFormatter.format("任务 [{}] 的数据新增配置不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
} else if (taskConfig.getActionType().equals(FlowAutoActionType.UPDATE)) {
this.verifyDestTableNameEmpty(taskConfig);
if (CollUtil.isEmpty(taskConfig.getUpdateDataList())) {
errorMessage = StrFormatter.format("任务 [{}] 的数据更新配置不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
} else if (taskConfig.getActionType().equals(FlowAutoActionType.DELETE)) {
this.verifyDestTableNameEmpty(taskConfig);
}
}
private void verifyDestTableNameEmpty(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getDestTableName())) {
String errorMessage = StrFormatter.format("任务 [{}] 的目标表不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
}
private void verifySrcTableNameEmpty(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getSrcTableName())) {
String errorMessage = StrFormatter.format("任务 [{}] 的源表不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
}
private void verifyHttpConfig(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getHttpRequestInfo().getUrl())) {
String errorMessage = StrFormatter.format("任务 [{}] 的URL不能为空", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
}
private void buildUserMapList(
List<FlowUserInfoVo> userInfoList, Set<String> loginNameSet, List<FlowUserInfoVo> userMapList) {
if (CollUtil.isEmpty(userInfoList)) {
return;
}
for (FlowUserInfoVo userInfo : userInfoList) {
if (!loginNameSet.contains(userInfo.getLoginName())) {
loginNameSet.add(userInfo.getLoginName());
userMapList.add(userInfo);
}
}
}
private FlowTaskExt buildTaskExtByAutoTask(Task task) {
FlowTaskExt flowTaskExt = new FlowTaskExt();
flowTaskExt.setTaskId(task.getId());
flowTaskExt.setGroupType(FlowConstant.GROUP_TYPE_AUTO_EXEC);
Map<String, List<ExtensionElement>> extensionMap = task.getExtensionElements();
List<ExtensionElement> autoConfigElements = extensionMap.get("taskInfo");
if (CollUtil.isEmpty(autoConfigElements)) {
return flowTaskExt;
}
ExtensionElement autoConfigElement = autoConfigElements.get(0);
Map<String, List<ExtensionAttribute>> attributesMap = autoConfigElement.getAttributes();
if (attributesMap != null) {
List<ExtensionAttribute> attributes = attributesMap.get("data");
if (CollUtil.isNotEmpty(attributes)) {
ExtensionAttribute attribute = attributes.get(0);
if (StrUtil.isNotBlank(attribute.getValue())) {
AutoTaskConfig taskConfig = JSONObject.parseObject(attribute.getValue(), AutoTaskConfig.class);
taskConfig.setTaskKey(task.getId());
taskConfig.setTaskName(task.getName());
flowTaskExt.setAutoConfigJson(JSON.toJSONString(taskConfig));
}
}
}
return flowTaskExt;
}
private void doBuildTaskExtList(FlowElement element, List<FlowTaskExt> flowTaskExtList) { private void doBuildTaskExtList(FlowElement element, List<FlowTaskExt> flowTaskExtList) {
if (element instanceof UserTask) { if (element instanceof UserTask) {
FlowTaskExt flowTaskExt = this.buildTaskExtByUserTask((UserTask) element); FlowTaskExt flowTaskExt = this.buildTaskExtByUserTask((UserTask) element);
flowTaskExtList.add(flowTaskExt); flowTaskExtList.add(flowTaskExt);
} else if (element instanceof ServiceTask || element instanceof ReceiveTask) {
FlowTaskExt flowTaskExt = this.buildTaskExtByAutoTask((Task) element);
flowTaskExtList.add(flowTaskExt);
} else if (element instanceof SubProcess) { } else if (element instanceof SubProcess) {
Collection<FlowElement> flowElements = ((SubProcess) element).getFlowElements(); Collection<FlowElement> flowElements = ((SubProcess) element).getFlowElements();
for (FlowElement element1 : flowElements) { for (FlowElement element1 : flowElements) {

View File

@@ -0,0 +1,68 @@
package com.orangeforms.common.flow.service.impl;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.flow.dao.FlowTransProducerMapper;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Date;
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("flowTransProducerService")
public class FlowTransProducerServiceImpl extends BaseService<FlowTransProducer, Long> implements FlowTransProducerService {
@Autowired
private FlowTransProducerMapper flowTransProducerMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Override
protected BaseDaoMapper<FlowTransProducer> mapper() {
return flowTransProducerMapper;
}
@Transactional(rollbackFor = Exception.class)
@Override
public FlowTransProducer saveNew(FlowTransProducer data) {
if (data.getTransId() == null) {
data.setTransId(idGenerator.nextLongId());
}
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData != null) {
data.setAppCode(tokenData.getAppCode());
data.setCreateLoginName(tokenData.getLoginName());
data.setCreateUsername(tokenData.getShowName());
}
data.setCreateTime(new Date());
data.setTraceId(MyCommonUtil.getTraceId());
flowTransProducerMapper.insert(data);
return data;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateById(FlowTransProducer data) {
return mapper().update(data, true) != 0;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean removeById(Serializable transId) {
return mapper().deleteById(transId) != 0;
}
}

View File

@@ -0,0 +1,656 @@
package com.orangeforms.common.flow.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.constant.FieldFilterType;
import com.orangeforms.common.core.constant.GlobalDeletedFlag;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.object.Tuple2;
import com.orangeforms.common.dbutil.object.DatasetFilter;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.dbutil.util.DataSourceUtil;
import com.orangeforms.common.flow.constant.FlowAutoActionType;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.model.FlowEntryVariable;
import com.orangeforms.common.flow.model.FlowTaskExt;
import com.orangeforms.common.flow.object.*;
import com.orangeforms.common.flow.service.FlowApiService;
import com.orangeforms.common.flow.service.FlowAutoVariableLogService;
import com.orangeforms.common.flow.service.FlowDblinkService;
import com.orangeforms.common.flow.service.FlowTaskExtService;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import static com.orangeforms.common.flow.object.AutoTaskConfig.*;
@Slf4j
@Component
public class AutoFlowHelper {
@Autowired
private FlowApiService flowApiService;
@Autowired
private FlowDblinkService flowDblinkService;
@Autowired
private FlowTaskExtService flowTaskExtService;
@Autowired
private FlowAutoVariableLogService flowAutoVariableLogService;
@Autowired
private FlowDataSourceUtil flowDataSourceUtil;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private RuntimeService runtimeService;
@Autowired
private RestTemplate restTemplate;
private static final String REGEX_VAR = "\\$\\{(.+?)\\}";
private static final ThreadLocal<JSONObject> START_AUTO_INIT_VARIABLES = ThreadLocal.withInitial(() -> null);
private static final List<FlowEntryVariable> SYSTEM_VARIABLES = CollUtil.newLinkedList();
static {
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("currentTime", "当前时间"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialUserId", "发起用户ID"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialLoginName", "发起用户登录名"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialShowName", "发起用户显示名"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialDeptId", "发起用户部门ID"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("tokenData", "Token令牌数据"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("logicNormal", "逻辑删除正常值"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("logicDelete", "逻辑删除删除值"));
}
/**
* 设置初始化变量,仅在启动自动化流程的时候存入。临时存入变量参数到线程本地化存储,以后任务监听器的读取。
*
* @param variables 自动化流程启动时的初始变量对象。
*/
public static void setStartAutoInitVariables(JSONObject variables) {
START_AUTO_INIT_VARIABLES.set(variables);
}
/**
* 获取自动化流程启动时传入的初始变量数据。
*
* @return 自动化流程启动时传入的初始变量数据。
*/
public static JSONObject getStartAutoInitVariables() {
return START_AUTO_INIT_VARIABLES.get();
}
/**
* 清空该存储数据,主动释放线程本地化存储资源。
*/
public static void clearStartAutoInitVariables() {
START_AUTO_INIT_VARIABLES.remove();
}
public static List<FlowEntryVariable> systemFlowEntryVariables() {
return SYSTEM_VARIABLES;
}
/**
* 获取实时的系统变量。
*
* @return 系统变量键值对对象。
*/
public JSONObject getRealtimeSystemVariables() {
JSONObject systemVariables = new JSONObject();
systemVariables.put("currentTime", new Date());
return systemVariables;
}
/**
* 获取非实时的系统变量。
*
* @return 系统变量键值对对象。
*/
public JSONObject getNonRealtimeSystemVariables() {
JSONObject systemVariables = this.getRealtimeSystemVariables();
TokenData tokenData = TokenData.takeFromRequest();
systemVariables.put("initialUserId", tokenData != null ? tokenData.getUserId() : null);
systemVariables.put("initialLoginName", tokenData != null ? tokenData.getLoginName() : null);
systemVariables.put("initialShowName", tokenData != null ? tokenData.getShowName() : null);
systemVariables.put("initialDeptId", tokenData != null ? tokenData.getDeptId() : null);
systemVariables.put("tokenData", tokenData != null ? tokenData.getToken() : null);
systemVariables.put("logicNormal", GlobalDeletedFlag.NORMAL);
systemVariables.put("logicDelete", GlobalDeletedFlag.DELETED);
return systemVariables;
}
/**
* 解析自动化任务配置对象。
*
* @param processDefinitionId 流程定义Id。
* @param taskKey 流程任务定义标识。
* @return 自动化任务的配置对象。
*/
public AutoTaskConfig parseAutoTaskConfig(String processDefinitionId, String taskKey) {
FlowTaskExt taskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(processDefinitionId, taskKey);
return JSON.parseObject(taskExt.getAutoConfigJson(), AutoTaskConfig.class);
}
/**
* 指定指定的任务。
*
* @param transId 自动化任务执行时的生产者流水号Id。
* @param taskConfig 自动化任务配置对象。
* @param d 当前的执行委托对象。
*/
public void executeTask(Long transId, AutoTaskConfig taskConfig, DelegateExecution d) {
JSONObject variableData = this.getAutomaticVariable(d.getProcessInstanceId());
FlowDblink destDblink = new FlowDblink();
destDblink.setDblinkId(taskConfig.getDestDblinkId());
destDblink.setDblinkType(taskConfig.getDestDblinkType());
SqlTable destTable = flowDblinkService.getDblinkTable(destDblink, taskConfig.getDestTableName());
if (taskConfig.getActionType().equals(FlowAutoActionType.ADD_NEW)) {
List<AutoExecData> execDataList = this.makeInsertAutoExecData(taskConfig, destTable, variableData);
this.doExecuteSql(execDataList);
} else if (taskConfig.getActionType().equals(FlowAutoActionType.UPDATE)) {
AutoExecData execData = this.makeUpdateAutoExecData(taskConfig, destTable, variableData);
this.doExecuteSql(CollUtil.newArrayList(execData));
} else if (taskConfig.getActionType().equals(FlowAutoActionType.DELETE)) {
AutoExecData execData = this.makeDeleteAutoExecData(taskConfig, destTable, variableData);
this.doExecuteSql(CollUtil.newArrayList(execData));
} else if (taskConfig.getActionType().equals(FlowAutoActionType.SELECT_ONE)) {
this.doQueryOne(taskConfig, variableData, d);
} else if (taskConfig.getActionType().equals(FlowAutoActionType.HTTP)) {
this.doHttpRequest(transId, taskConfig, variableData, d);
}
}
private void doExecuteSql(List<AutoExecData> autoExecDataList) {
if (CollUtil.isEmpty(autoExecDataList)) {
return;
}
Connection connection = null;
try {
connection = flowDataSourceUtil.getConnection(autoExecDataList.get(0).getDblinkId());
connection.setAutoCommit(false);
for (AutoExecData autoExecData : autoExecDataList) {
flowDataSourceUtil.execute(connection, autoExecData.getSql(), autoExecData.getParams());
}
connection.commit();
} catch (Exception e) {
if (connection != null) {
try {
connection.rollback();
} catch (SQLException ex) {
log.error(e.getMessage(), e);
}
}
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
}
}
}
private List<AutoExecData> makeInsertAutoExecData(
AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
List<AutoExecData> resultList = new LinkedList<>();
SqlTableColumn primaryKeyColumn = destTable.getColumnList().stream()
.filter(c -> BooleanUtil.isTrue(c.getPrimaryKey())).findFirst().orElse(null);
Map<String, SqlTableColumn> destColumnMap =
destTable.getColumnList().stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, c -> c));
ValueInfo pkValueInfo = null;
if (primaryKeyColumn != null) {
pkValueInfo = taskConfig.getInsertDataList().stream()
.filter(valueInfo -> valueInfo.getDestColumnName().equals(primaryKeyColumn.getColumnName()))
.findFirst().orElse(null);
}
//查询源数据。
List<Map<String, Object>> srcResultList = this.getSrcTableDataList(taskConfig, variableData);
if (CollUtil.isEmpty(srcResultList)) {
srcResultList.add(new HashMap<>(1));
}
for (Map<String, Object> srcResult : srcResultList) {
List<Object> params = new LinkedList<>();
StringBuilder sqlBuilder = new StringBuilder(1024);
sqlBuilder.append("INSERT INTO ").append(taskConfig.getDestTableName()).append("(");
if (primaryKeyColumn != null) {
Object param = this.calculatePrimaryKeyParam(primaryKeyColumn, pkValueInfo, srcResult);
if (param != null) {
sqlBuilder.append(primaryKeyColumn.getColumnName()).append(",");
params.add(param);
}
}
for (ValueInfo valueInfo : taskConfig.getInsertDataList()) {
if (pkValueInfo == null || !pkValueInfo.equals(valueInfo)) {
sqlBuilder.append(valueInfo.getDestColumnName()).append(",");
SqlTableColumn column = destColumnMap.get(valueInfo.getDestColumnName());
String destFieldType = flowDataSourceUtil.convertToJavaType(column, taskConfig.getDestDblinkType());
Object value = this.calculateValue(
valueInfo.getType(), valueInfo.getSrcValue(), destFieldType, srcResult, variableData);
params.add(value);
}
}
sqlBuilder.setCharAt(sqlBuilder.length() - 1, ')');
sqlBuilder.append(" VALUES(");
params.forEach(p -> sqlBuilder.append("?,"));
sqlBuilder.setCharAt(sqlBuilder.length() - 1, ')');
AutoExecData execData = new AutoExecData();
execData.setDblinkId(taskConfig.getDestDblinkId());
execData.setSql(sqlBuilder.toString());
execData.setParams(params);
resultList.add(execData);
}
return resultList;
}
private AutoExecData makeUpdateAutoExecData(AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
Map<String, SqlTableColumn> destColumnMap =
destTable.getColumnList().stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, c -> c));
List<Object> params = new LinkedList<>();
StringBuilder sqlBuilder = new StringBuilder(1024);
sqlBuilder.append("UPDATE ").append(taskConfig.getDestTableName()).append(" SET ");
for (ValueInfo valueInfo : taskConfig.getUpdateDataList()) {
sqlBuilder.append(valueInfo.getDestColumnName()).append(" = ?,");
SqlTableColumn column = destColumnMap.get(valueInfo.getDestColumnName());
String destFieldType = flowDataSourceUtil.convertToJavaType(column, taskConfig.getDestDblinkType());
Object value = this.calculateValue(
valueInfo.getType(), valueInfo.getSrcValue(), destFieldType, null, variableData);
params.add(value);
}
sqlBuilder.setCharAt(sqlBuilder.length() - 1, ' ');
Tuple2<String, List<Object>> result = this.calculateWhereClause(taskConfig, destTable, variableData);
sqlBuilder.append(result.getFirst());
CollUtil.addAll(params, result.getSecond());
AutoExecData execData = new AutoExecData();
execData.setDblinkId(taskConfig.getDestDblinkId());
execData.setSql(sqlBuilder.toString());
execData.setParams(params);
return execData;
}
private AutoExecData makeDeleteAutoExecData(AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
if (StrUtil.isNotBlank(taskConfig.getLogicDeleteField())) {
List<ValueInfo> updateDataList = new LinkedList<>();
ValueInfo logicDeleteValueInfo = new ValueInfo();
logicDeleteValueInfo.setDestColumnName(taskConfig.getLogicDeleteField());
logicDeleteValueInfo.setType(AutoTaskConfig.FIXED_VALUE);
logicDeleteValueInfo.setSrcValue(String.valueOf(GlobalDeletedFlag.DELETED));
updateDataList.add(logicDeleteValueInfo);
taskConfig.setUpdateDataList(updateDataList);
return this.makeUpdateAutoExecData(taskConfig, destTable, variableData);
}
StringBuilder sqlBuilder = new StringBuilder(1024);
sqlBuilder.append("DELETE FROM ").append(taskConfig.getDestTableName());
Tuple2<String, List<Object>> result = this.calculateWhereClause(taskConfig, destTable, variableData);
sqlBuilder.append(result.getFirst());
AutoExecData execData = new AutoExecData();
execData.setDblinkId(taskConfig.getDestDblinkId());
execData.setSql(sqlBuilder.toString());
execData.setParams(result.getSecond());
return execData;
}
private void doQueryOne(AutoTaskConfig taskConfig, JSONObject variableData, DelegateExecution d) {
List<Map<String, Object>> srcResultList = this.getSrcTableDataList(taskConfig, variableData);
Map<String, Object> srcResult = null;
if (CollUtil.isNotEmpty(srcResultList)) {
srcResult = srcResultList.get(0);
}
this.refreshAutoVariableLog(d, taskConfig.getTaskKey(), srcResult);
}
private void doHttpRequest(Long transId, AutoTaskConfig taskConfig, JSONObject variableData, DelegateExecution d) {
AutoHttpRequestInfo req = taskConfig.getHttpRequestInfo();
AutoHttpResponseData resp = taskConfig.getHttpResponnseData();
String body = this.buildRequestBody(req, variableData);
HttpHeaders headers = this.buildHttpHeaders(req, variableData);
headers.add(ApplicationConstant.HTTP_HEADER_TRANS_ID, transId.toString());
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(req.getUrl());
if (CollUtil.isNotEmpty(req.getUrlParamList())) {
for (ValueInfo valueInfo : req.getUrlParamList()) {
String paramValue = this.calculateValue(valueInfo.getType(), valueInfo.getSrcValue(), variableData);
uriBuilder.queryParam(valueInfo.getKey(), paramValue);
}
}
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(
uriBuilder.encode().toUriString(), HttpMethod.valueOf(req.getHttpMethod()), httpEntity, JSONObject.class);
try {
this.handleHttpResponseFail(req, resp, responseEntity);
this.refreshAutoVariableLog(d, taskConfig.getTaskKey(), responseEntity.getBody());
} catch (MyRuntimeException e) {
if (!resp.getFailHandleType().equals(AutoHttpResponseData.CONTINUE_ON_FAIL)) {
throw e;
}
log.error(e.getMessage(), e);
}
}
private Object calculatePrimaryKeyParam(
SqlTableColumn primaryKeyColumn, ValueInfo pkValueInfo, Map<String, Object> srcResult) {
// 说明目标表的主键值来自于源表的字段值。
if (pkValueInfo != null) {
return srcResult.get(pkValueInfo.getSrcValue());
}
if (BooleanUtil.isFalse(primaryKeyColumn.getAutoIncrement())) {
return primaryKeyColumn.getStringPrecision() == null ? idGenerator.nextLongId() : idGenerator.nextStringId();
}
return null;
}
private List<Map<String, Object>> getSrcTableDataList(AutoTaskConfig taskConfig, JSONObject variableData) {
if (ObjectUtil.isEmpty(taskConfig.getSrcDblinkId()) || StrUtil.isBlank(taskConfig.getSrcTableName())) {
return new LinkedList<>();
}
this.appendLogicDeleteFilter(taskConfig);
FlowDblink srcDblink = new FlowDblink();
srcDblink.setDblinkId(taskConfig.getSrcDblinkId());
srcDblink.setDblinkType(taskConfig.getSrcDblinkType());
SqlTable srcTable = flowDblinkService.getDblinkTable(srcDblink, taskConfig.getSrcTableName());
StringBuilder sqlBuilder = new StringBuilder(256);
String selectFields = CollUtil.isEmpty(taskConfig.getSelectFieldList())
? "*" : CollUtil.join(taskConfig.getSelectFieldList(), ",");
sqlBuilder.append("SELECT ").append(selectFields).append(" FROM ").append(srcTable.getTableName());
if (taskConfig.getSrcFilterType().equals(AutoTaskConfig.SRC_FILTER_SQL)) {
Tuple2<String, List<Object>> result = this.calcualteCustomSqlFilter(taskConfig.getSrcFilterSql(), variableData);
if (StrUtil.isNotBlank(result.getFirst())) {
sqlBuilder.append(result.getFirst());
}
try {
return flowDataSourceUtil.query(taskConfig.getSrcDblinkId(), sqlBuilder.toString(), result.getSecond());
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
}
}
DatasetFilter dataFilter = null;
if (CollUtil.isNotEmpty(taskConfig.getSrcFilterList())) {
dataFilter = this.calculateDatasetFilter(
srcTable, taskConfig.getSrcDblinkType(), taskConfig.getSrcFilterList(), variableData);
}
try {
Tuple2<String, List<Object>> result =
flowDataSourceUtil.buildWhereClauseByFilters(taskConfig.getSrcDblinkId(), dataFilter);
sqlBuilder.append(result.getFirst());
return flowDataSourceUtil.query(taskConfig.getSrcDblinkId(), sqlBuilder.toString(), result.getSecond());
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
}
}
private void refreshAutoVariableLog(DelegateExecution d, String outputVariableName, Map<String, Object> newResult) {
if (StrUtil.isBlank(outputVariableName)) {
return;
}
runtimeService.setVariable(d.getId(), outputVariableName, newResult);
FlowAutoVariableLog autoVariableLog =
flowAutoVariableLogService.getAutoVariableByProcessInstanceId(d.getProcessInstanceId());
JSONObject latestVariableData = JSON.parseObject(autoVariableLog.getVariableData());
latestVariableData.put(outputVariableName, newResult);
autoVariableLog.setVariableData(JSON.toJSONString(latestVariableData));
flowAutoVariableLogService.updateById(autoVariableLog);
}
private Tuple2<String, List<Object>> calculateWhereClause(AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
if (taskConfig.getDestFilterType().equals(AutoTaskConfig.SRC_FILTER_SQL)) {
return this.calcualteCustomSqlFilter(taskConfig.getDestFilterSql(), variableData);
}
if (CollUtil.isNotEmpty(taskConfig.getDestFilterList())) {
DatasetFilter dataFilter = this.calculateDatasetFilter(
destTable, taskConfig.getDestDblinkType(), taskConfig.getDestFilterList(), variableData);
return flowDataSourceUtil.buildWhereClauseByFilters(taskConfig.getDestDblinkId(), dataFilter);
}
return new Tuple2<>(StrUtil.EMPTY, new LinkedList<>());
}
private Tuple2<String, List<Object>> calcualteCustomSqlFilter(String filterSql, JSONObject variableData) {
Tuple2<String, List<String>> result = this.findAndReplaceAllVariables(filterSql);
String whereClause = result.getFirst();
if (StrUtil.isNotBlank(whereClause)) {
whereClause = DataSourceUtil.SQL_WHERE + whereClause;
}
List<Object> params = this.extractVariableParamsByVariable(result.getSecond(), variableData);
return new Tuple2<>(whereClause, params);
}
private DatasetFilter calculateDatasetFilter(
SqlTable table, Integer dblinkType, List<FilterInfo> filterInfoList, JSONObject variableData) {
Map<String, SqlTableColumn> columnMap = table.getColumnList()
.stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, c -> c));
DatasetFilter dataFilter = new DatasetFilter();
filterInfoList.forEach(filterInfo -> {
DatasetFilter.FilterInfo filter = new DatasetFilter.FilterInfo();
filter.setFilterType(filterInfo.getFilterType());
filter.setParamName(filterInfo.getFilterColumnName());
SqlTableColumn column = columnMap.get(filterInfo.getFilterColumnName());
String fieldType = flowDataSourceUtil.convertToJavaType(column, dblinkType);
if (filterInfo.getFilterType().equals(FieldFilterType.IN)) {
filter.setParamValueList(flowDataSourceUtil.convertToColumnValues(fieldType, filterInfo.getFilterValue()));
} else {
Object convertedValue = calculateValue(
filterInfo.getValueType(), filterInfo.getFilterValue(), fieldType, null, variableData);
filter.setParamValue(convertedValue);
}
dataFilter.add(filter);
});
return dataFilter;
}
private void appendLogicDeleteFilter(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getLogicDeleteField())) {
return;
}
if (taskConfig.getSrcFilterType().equals(SRC_FILTER_SQL)) {
StringBuilder sb = new StringBuilder(512);
if (StrUtil.isNotBlank(taskConfig.getSrcFilterSql())) {
sb.append("(").append(taskConfig.getSrcFilterSql())
.append(") AND ")
.append(taskConfig.getLogicDeleteField())
.append("=")
.append(GlobalDeletedFlag.NORMAL);
} else {
sb.append(taskConfig.getLogicDeleteField()).append("=").append(GlobalDeletedFlag.NORMAL);
}
taskConfig.setSrcFilterSql(sb.toString());
} else {
List<FilterInfo> filterInfoList = taskConfig.getSrcFilterList();
if (filterInfoList == null) {
filterInfoList = new LinkedList<>();
}
FilterInfo logicDeleteFilter = new FilterInfo();
logicDeleteFilter.setFilterColumnName(taskConfig.getLogicDeleteField());
logicDeleteFilter.setFilterType(FieldFilterType.EQUAL);
logicDeleteFilter.setValueType(AutoTaskConfig.FIXED_VALUE);
logicDeleteFilter.setFilterValue(String.valueOf(GlobalDeletedFlag.NORMAL));
filterInfoList.add(logicDeleteFilter);
taskConfig.setSrcFilterList(filterInfoList);
}
}
private String buildRequestBody(AutoHttpRequestInfo req, JSONObject variableData) {
String body = null;
if (StrUtil.equals(req.getBodyType(), AutoHttpRequestInfo.BODY_TYPE_RAW)) {
body = this.replaceAllVariables(req.getBodyData(), variableData);
} else {
StringBuilder sb = new StringBuilder(256);
if (CollUtil.isNotEmpty(req.getFormDataList())) {
for (ValueInfo valueInfo : req.getFormDataList()) {
String value = this.calculateValue(valueInfo.getType(), valueInfo.getSrcValue(), variableData);
sb.append(valueInfo.getKey()).append("=").append(value).append("&");
}
body = sb.substring(0, sb.length() - 1);
}
}
return body;
}
private HttpHeaders buildHttpHeaders(AutoHttpRequestInfo req, JSONObject variableData) {
HttpHeaders headers = new HttpHeaders();
if (CollUtil.isNotEmpty(req.getHeaderList())) {
for (ValueInfo valueInfo : req.getHeaderList()) {
String value = this.calculateValue(valueInfo.getType(), valueInfo.getSrcValue(), variableData);
headers.add(valueInfo.getKey(), value);
}
}
if (StrUtil.equals(req.getBodyType(), AutoHttpRequestInfo.BODY_TYPE_RAW)) {
headers.setContentType(MediaType.APPLICATION_JSON);
} else {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
}
return headers;
}
private void handleHttpResponseFail(
AutoHttpRequestInfo req, AutoHttpResponseData resp, ResponseEntity<JSONObject> responseEntity) {
Set<Integer> successStatusCodes = CollUtil.newHashSet(HttpStatus.HTTP_OK);
if (StrUtil.isNotBlank(resp.getSuccessStatusCode())) {
successStatusCodes = StrUtil.split(resp.getSuccessStatusCode(), StrUtil.COMMA)
.stream().map(Integer::valueOf).collect(Collectors.toSet());
}
// 先判断HttpStatus是否正确。
if (!CollUtil.contains(successStatusCodes, responseEntity.getStatusCode().value())) {
String cancelReason = StrFormatter.format(
"Failed to Rquest Url [{}] with StatusCode [{}]", req.getUrl(), responseEntity.getStatusCode().value());
throw new MyRuntimeException(cancelReason);
}
// 如果没有配置应答体中标记是否成功的字段,则视为成功。
if (StrUtil.isBlank(resp.getSuccessBodyField())) {
return;
}
JSONObject responseData = responseEntity.getBody();
String successFlagField = JSONPath.compile(resp.getSuccessBodyField()).eval(responseData, String.class);
// 如果应答体中标记是否成功的字段你不存在或者是应答体中标记为成功的字段值为true则视为成功。
if (StrUtil.isBlank(successFlagField)
|| StrUtil.equals(successFlagField, "[]")
|| BooleanUtil.toBooleanObject(successFlagField).equals(Boolean.TRUE)) {
return;
}
// 开始处理失败场景。
String cancelReason;
if (StrUtil.isNotBlank(resp.getErrorMessageBodyField())) {
String errorMsgPath = "$." + resp.getErrorMessageBodyField();
String errorMsg = JSONPath.compile(errorMsgPath).eval(responseData, String.class);
cancelReason = StrFormatter.format(
"Failed to Rquest Url [{}] with errorMsg [{}]", req.getUrl(), errorMsg);
} else {
cancelReason = StrFormatter.format("Failed to Rquest Url [{}]", req.getUrl());
}
throw new MyRuntimeException(cancelReason);
}
private Tuple2<String, List<String>> findAndReplaceAllVariables(String s) {
List<String> variables = ReUtil.findAll(REGEX_VAR, s, 0);
if (CollUtil.isNotEmpty(variables)) {
s = s.replaceAll(REGEX_VAR, "?");
variables = variables.stream().map(v -> v.substring(2, v.length() - 1)).collect(Collectors.toList());
}
return new Tuple2<>(s, variables);
}
private String replaceAllVariables(String s, JSONObject variableData) {
if (StrUtil.isNotBlank(s)) {
List<String> variables = ReUtil.findAll(REGEX_VAR, s, 0);
if (CollUtil.isNotEmpty(variables)) {
for (String v : variables) {
s = StrUtil.replace(s, v, variableData.getString(v.substring(2, v.length() - 1)));
}
}
}
return s;
}
private JSONObject getAutomaticVariable(String processInstanceId) {
JSONObject variableData = getStartAutoInitVariables();
if (variableData == null) {
FlowAutoVariableLog v = flowAutoVariableLogService.getAutoVariableByProcessInstanceId(processInstanceId);
if (v != null) {
variableData = JSON.parseObject(v.getVariableData());
}
}
JSONObject systemVariables = this.getRealtimeSystemVariables();
if (variableData == null) {
return systemVariables;
}
variableData.putAll(systemVariables);
return variableData;
}
private List<Object> extractVariableParamsByVariable(List<String> variableNames, JSONObject variableData) {
List<Object> resultList = new LinkedList<>();
if (CollUtil.isEmpty(variableNames) || MapUtil.isEmpty(variableData)) {
return resultList;
}
for (String name : variableNames) {
Object value = this.verifyAndGetVariableExist(name, variableData);
if (value != null) {
resultList.add(value);
}
}
return resultList;
}
private Object calculateValue(
Integer valueType, String value, String fieldType, Map<String, Object> srcResult, JSONObject variableData) {
if (valueType.equals(AutoTaskConfig.FIXED_VALUE)) {
return flowDataSourceUtil.convertToColumnValue(fieldType, value);
} else if (valueType.equals(AutoTaskConfig.COLUMN_VALUE)) {
return srcResult.get(value);
}
Object variableValue = this.verifyAndGetVariableExist(value, variableData);
return flowDataSourceUtil.convertToColumnValue(fieldType, (Serializable) variableValue);
}
private String calculateValue(Integer valueType, String value, JSONObject variableData) {
if (valueType.equals(AutoTaskConfig.FIXED_VALUE)) {
return value;
}
Object variableValue = this.verifyAndGetVariableExist(value, variableData);
return variableValue == null ? null : variableValue.toString();
}
private Object verifyAndGetVariableExist(String name, JSONObject variableData) {
String variableName = name;
if (StrUtil.contains(name, StrUtil.DOT)) {
variableName = StrUtil.subBefore(name, StrUtil.DOT, false);
}
if (!variableData.containsKey(variableName)) {
throw new MyRuntimeException(StrFormatter.format("变量值 [{}] 不存在!", name));
}
if (!StrUtil.contains(name, StrUtil.DOT)) {
return variableData.get(variableName);
}
JSONObject variableObject = variableData.getJSONObject(variableName);
if (variableObject == null) {
return null;
}
String variablePath = StrUtil.subAfter(name, StrUtil.DOT, false);
return JSONPath.compile(variablePath).eval(variableObject);
}
}

View File

@@ -0,0 +1,47 @@
package com.orangeforms.common.flow.util;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.dbutil.provider.DataSourceProvider;
import com.orangeforms.common.dbutil.util.DataSourceUtil;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.service.FlowDblinkService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 工作流模块动态加载的数据源工具类。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@Component
public class FlowDataSourceUtil extends DataSourceUtil {
@Autowired
private FlowDblinkService dblinkService;
@Override
protected int getDblinkTypeByDblinkId(Long dblinkId) {
DataSourceProvider provider = this.dblinkProviderMap.get(dblinkId);
if (provider != null) {
return provider.getDblinkType();
}
FlowDblink dblink = dblinkService.getById(dblinkId);
if (dblink == null) {
throw new MyRuntimeException("Flow DblinkId [" + dblinkId + "] doesn't exist!");
}
this.dblinkProviderMap.put(dblinkId, this.getProvider(dblink.getDblinkType()));
return dblink.getDblinkType();
}
@Override
protected String getDblinkConfigurationByDblinkId(Long dblinkId) {
FlowDblink dblink = dblinkService.getById(dblinkId);
if (dblink == null) {
throw new MyRuntimeException("Flow DblinkId [" + dblinkId + "] doesn't exist!");
}
return dblink.getConfiguration();
}
}

View File

@@ -0,0 +1,59 @@
package com.orangeforms.common.flow.util;
import com.alibaba.fastjson.JSON;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
/**
* 流程监听器发送spring事件的帮助类。
* 注意流程的监听器不是bean对象不能发送和捕捉事件因此需要借助该类完成。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@Component
public class ListenerEventPublishHelper {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private RuntimeService runtimeService;
@Autowired
private FlowTransProducerService flowTransProducerService;
public <T> void publishEvent(T data) {
eventPublisher.publishEvent(data);
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void doHandle(FlowTransProducer producerData) {
Map<String, Object> transVariableMap = new HashMap<>(1);
transVariableMap.put(FlowConstant.AUTO_FLOW_TRANS_PRODUCER_VAR, producerData);
Executors.newSingleThreadExecutor().submit(() -> this.triggerReceiveTask(producerData, transVariableMap));
}
private void triggerReceiveTask(FlowTransProducer producerData, Map<String, Object> transVariableMap) {
try {
runtimeService.trigger(producerData.getExecutionId(), null, transVariableMap);
} catch (Exception e) {
log.error("Failed to commit automatic business data [** " + JSON.toJSONString(producerData) + " **]", e);
producerData.setErrorReason(e.getMessage());
flowTransProducerService.updateById(producerData);
throw new MyRuntimeException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,84 @@
package com.orangeforms.common.flow.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
import java.util.Map;
/**
* 工作流数据表所在数据库链接VO对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Schema(description = "工作流数据表所在数据库链接VO对象")
@Data
public class FlowDblinkVo {
/**
* 主键Id。
*/
@Schema(description = "主键Id")
private Long dblinkId;
/**
* 应用编码。为空时,表示非第三方应用接入。
*/
@Schema(description = "应用编码。为空时,表示非第三方应用接入")
private String appCode;
/**
* 链接中文名称。
*/
@Schema(description = "链接中文名称")
private String dblinkName;
/**
* 链接描述。
*/
@Schema(description = "链接描述")
private String dblinkDescription;
/**
* 配置信息。
*/
@Schema(description = "配置信息")
private String configuration;
/**
* 数据库链接类型。
*/
@Schema(description = "数据库链接类型")
private Integer dblinkType;
/**
* 更新者。
*/
@Schema(description = "更新者")
private Long updateUserId;
/**
* 更新时间。
*/
@Schema(description = "更新时间")
private Date updateTime;
/**
* 创建者。
*/
@Schema(description = "创建者")
private Long createUserId;
/**
* 创建时间。
*/
@Schema(description = "创建时间")
private Date createTime;
/**
* 数据库链接类型常量字典关联数据。
*/
@Schema(description = "数据库链接类型常量字典关联数据")
private Map<String, Object> dblinkTypeDictMap;
}

Binary file not shown.

View File

@@ -1290,6 +1290,7 @@ CREATE TABLE `zz_flow_entry` (
`bpmn_xml` longtext COMMENT '流程定义的xml', `bpmn_xml` longtext COMMENT '流程定义的xml',
`diagram_type` int NOT NULL COMMENT '流程图类型', `diagram_type` int NOT NULL COMMENT '流程图类型',
`bind_form_type` int NOT NULL COMMENT '绑定表单类型', `bind_form_type` int NOT NULL COMMENT '绑定表单类型',
`flow_type` int NOT NULL COMMENT '流程类型',
`page_id` bigint DEFAULT NULL COMMENT '在线表单的页面Id', `page_id` bigint DEFAULT NULL COMMENT '在线表单的页面Id',
`default_form_id` bigint DEFAULT NULL COMMENT '在线表单Id', `default_form_id` bigint DEFAULT NULL COMMENT '在线表单Id',
`default_router_name` varchar(255) DEFAULT NULL COMMENT '静态表单的缺省路由名称', `default_router_name` varchar(255) DEFAULT NULL COMMENT '静态表单的缺省路由名称',
@@ -1517,6 +1518,7 @@ CREATE TABLE `zz_flow_task_ext` (
`candidate_usernames` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '保存候选组用户名数据', `candidate_usernames` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '保存候选组用户名数据',
`copy_list_json` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '抄送相关的数据', `copy_list_json` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '抄送相关的数据',
`extra_data_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '用户任务的扩展属性存储为JSON的字符串格式', `extra_data_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '用户任务的扩展属性存储为JSON的字符串格式',
`auto_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '自动化任务配置数据存储为JSON的字符串格式',
PRIMARY KEY (`process_definition_id`,`task_id`) USING BTREE PRIMARY KEY (`process_definition_id`,`task_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程流程图任务扩展表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程流程图任务扩展表';
@@ -1579,6 +1581,72 @@ CREATE TABLE `zz_flow_work_order_ext` (
KEY `idx_work_order_id` (`work_order_id`) USING BTREE KEY `idx_work_order_id` (`work_order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程工单扩展表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程工单扩展表';
-- ----------------------------
-- Table structure for zz_flow_auto_variable_log
-- ----------------------------
DROP TABLE IF EXISTS `zz_flow_auto_variable_log`;
CREATE TABLE `zz_flow_auto_variable_log` (
`id` bigint NOT NULL COMMENT '主键Id',
`process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '流程实例Id',
`execution_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '执行实例Id',
`task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务Id',
`task_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务标识',
`trace_id` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '当前请求的traceId',
`variable_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '变量数据',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_process_instance_id` (`process_instance_id`,`task_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流程变量日志表';
-- ----------------------------
-- Table structure for zz_flow_dblink
-- ----------------------------
DROP TABLE IF EXISTS `zz_flow_dblink`;
CREATE TABLE `zz_flow_dblink` (
`dblink_id` bigint NOT NULL COMMENT '主键Id',
`app_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '应用编码',
`dblink_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '链接中文名称',
`dblink_description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '链接描述',
`dblink_type` int NOT NULL COMMENT '数据源类型',
`configuration` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '配置信息',
`create_time` datetime NOT NULL COMMENT '创建时间',
`create_user_id` bigint NOT NULL COMMENT '创建者',
`update_time` datetime NOT NULL COMMENT '更新时间',
`update_user_id` bigint NOT NULL COMMENT '更新者',
PRIMARY KEY (`dblink_id`) USING BTREE,
KEY `idx_dblink_type` (`dblink_type`) USING BTREE,
KEY `idx_app_code` (`app_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='在线表单数据库链接表';
-- ----------------------------
-- Table structure for zz_flow_trans_producer
-- ----------------------------
DROP TABLE IF EXISTS `zz_flow_trans_producer`;
CREATE TABLE `zz_flow_trans_producer` (
`trans_id` bigint NOT NULL COMMENT '主键Id',
`app_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '应用Id',
`dblink_id` bigint DEFAULT NULL COMMENT '数据库链接Id',
`process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '流程实例Id',
`execution_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '执行实例Id',
`task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务Id',
`task_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务标识',
`task_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '任务名称',
`task_comment` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '批注内容',
`url` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '当前请求的url',
`init_method` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '创建该事务性事件对象的初始方法',
`trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '当前请求的traceId',
`sql_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '和SQL操作相关的数据',
`auto_task_config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '自动化任务需要执行的数据',
`try_times` int NOT NULL COMMENT '尝试次数',
`error_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '提交业务数据时的错误信息',
`create_login_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建者登录名',
`create_username` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建者中文用户名',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`trans_id`) USING BTREE,
KEY `idx_app_code` (`app_code`) USING BTREE,
KEY `idx_process_instance_id` (`process_instance_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ---------------------------- -- ----------------------------
-- Table structure for zz_global_dict -- Table structure for zz_global_dict
-- ---------------------------- -- ----------------------------

View File

@@ -78,6 +78,10 @@ public final class ApplicationConstant {
* 请求头跟踪id名。 * 请求头跟踪id名。
*/ */
public static final String HTTP_HEADER_TRACE_ID = "traceId"; public static final String HTTP_HEADER_TRACE_ID = "traceId";
/**
* 请求头业务流水号id名。
*/
public static final String HTTP_HEADER_TRANS_ID = "transId";
/** /**
* 请求头菜单Id。 * 请求头菜单Id。
*/ */

View File

@@ -434,6 +434,19 @@ public class MyCommonUtil {
return builder.toString(); return builder.toString();
} }
/**
* 获取当前请求的traceId。
*
* @return 当前请求的traceId。
*/
public static String getTraceId() {
HttpServletRequest request = ContextUtil.getHttpRequest();
if (request == null) {
return null;
}
return request.getHeader(ApplicationConstant.HTTP_HEADER_TRACE_ID);
}
/** /**
* 私有构造函数,明确标识该常量类的作用。 * 私有构造函数,明确标识该常量类的作用。
*/ */

View File

@@ -1,11 +1,14 @@
package com.orangeforms.common.dbutil.util; package com.orangeforms.common.dbutil.util;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.constant.FieldFilterType; import com.orangeforms.common.core.constant.FieldFilterType;
import com.orangeforms.common.core.exception.InvalidDblinkTypeException; import com.orangeforms.common.core.exception.InvalidDblinkTypeException;
@@ -30,6 +33,7 @@ import net.sf.jsqlparser.statement.select.SelectItem;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.Serializable;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -50,12 +54,12 @@ public abstract class DataSourceUtil {
private static final Map<Integer, DataSourceProvider> PROVIDER_MAP = new HashMap<>(5); private static final Map<Integer, DataSourceProvider> PROVIDER_MAP = new HashMap<>(5);
protected final Map<Long, DataSourceProvider> dblinkProviderMap = new ConcurrentHashMap<>(4); protected final Map<Long, DataSourceProvider> dblinkProviderMap = new ConcurrentHashMap<>(4);
private static final String SQL_SELECT = " SELECT "; public static final String SQL_SELECT = " SELECT ";
private static final String SQL_SELECT_FROM = " SELECT * FROM ("; public static final String SQL_SELECT_FROM = " SELECT * FROM (";
private static final String SQL_AS_TMP = " ) tmp "; public static final String SQL_AS_TMP = " ) tmp ";
private static final String SQL_ORDER_BY = " ORDER BY "; public static final String SQL_ORDER_BY = " ORDER BY ";
private static final String SQL_AND = " AND "; public static final String SQL_AND = " AND ";
private static final String SQL_WHERE = " WHERE "; public static final String SQL_WHERE = " WHERE ";
private static final String LOG_PREPARING_FORMAT = "==> Preparing: {}"; private static final String LOG_PREPARING_FORMAT = "==> Preparing: {}";
private static final String LOG_PARMS_FORMAT = "==> Parameters: {}"; private static final String LOG_PARMS_FORMAT = "==> Parameters: {}";
private static final String LOG_TOTAL_FORMAT = "<== Total: {}"; private static final String LOG_TOTAL_FORMAT = "<== Total: {}";
@@ -356,6 +360,27 @@ public abstract class DataSourceUtil {
return this.getDataListInternnally(dblinkId, provider, sqlCount, sql, datasetParam, paramList); return this.getDataListInternnally(dblinkId, provider, sqlCount, sql, datasetParam, paramList);
} }
/**
* 执行包含参数变量的增删改操作。
*
* @param connection 数据库链接。
* @param sql SQL语句。
* @param paramList 参数列表。
* @return 影响的行数。
*/
public int execute(Connection connection, String sql, List<Object> paramList) {
try (PreparedStatement stat = connection.prepareStatement(sql)) {
for (int i = 0; i < paramList.size(); i++) {
stat.setObject(i + 1, paramList.get(i));
}
stat.execute();
return stat.getUpdateCount();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
}
}
/** /**
* 在指定数据库链接上执行查询语句,并返回指定映射对象类型的单条数据对象。 * 在指定数据库链接上执行查询语句,并返回指定映射对象类型的单条数据对象。
* *
@@ -416,6 +441,87 @@ public abstract class DataSourceUtil {
} }
} }
/**
* 根据数据库链接的类型将数据表字段类型转换为Java的字段属性类型。
*
* @param column 数据表字段对象。
* @param dblinkType 数据库链接类型。
* @return 返回与Java字段属性的类型名。
*/
public String convertToJavaType(SqlTableColumn column, int dblinkType) {
return this.convertToJavaType(
column.getColumnType(), column.getNumericPrecision(), column.getNumericScale(), dblinkType);
}
/**
* 根据数据库链接的类型将数据表字段类型转换为Java的字段属性类型。
*
* @param columnType 表字段类型。
* @param numericPrecision 数值的精度。
* @param numericScale 数值的刻度。
* @param dblinkType 数据库链接类型。
* @return 返回与Java字段属性的类型名。
*/
public String convertToJavaType(String columnType, Integer numericPrecision, Integer numericScale, int dblinkType) {
DataSourceProvider provider = this.getProvider(dblinkType);
if (provider == null) {
throw new MyRuntimeException("Unsupported Data Type");
}
return provider.convertColumnTypeToJavaType(columnType, numericPrecision, numericScale);
}
/**
* 根据Java字段属性的类型转换参数中的values值到与fieldType匹配的值类型数据列表。
*
* @param fieldType Java字段属性值。
* @param values 字符串类型的参数值列表该参数为JSON数组。
* @return 目标类型的参数值列表。
*/
public List<Serializable> convertToColumnValues(String fieldType, String values) {
List<Serializable> valueList = new LinkedList<>();
if (StrUtil.isBlank(values)) {
return valueList;
}
JSONArray valueArray = JSON.parseArray(values);
for (int i = 0; i < valueArray.size(); i++) {
String v = valueArray.getString(i);
valueList.add(this.convertToColumnValue(fieldType, v));
}
return valueList;
}
/**
* 根据Java字段属性的类型转换参数中的value值到与fieldType匹配的值类型。
*
* @param fieldType Java字段属性值。
* @param value 参数值。
* @return 目标类型的参数值。
*/
public Serializable convertToColumnValue(String fieldType, Serializable value) {
if (value == null) {
return null;
}
switch (fieldType) {
case "Long":
return Convert.toLong(value);
case "Integer":
return Convert.toInt(value);
case "BigDecimal":
return Convert.toBigDecimal(value);
case "Double":
return Convert.toDouble(value);
case "Boolean":
return Convert.toBool(value);
case "Date":
return value;
case "String":
return value.toString();
default:
break;
}
return null;
}
/** /**
* 计算过滤从句和过滤参数。 * 计算过滤从句和过滤参数。
* *

View File

@@ -20,6 +20,11 @@
<artifactId>common-satoken</artifactId> <artifactId>common-satoken</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.orangeforms</groupId>
<artifactId>common-dbutil</artifactId>
<version>1.0.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.orangeforms</groupId> <groupId>com.orangeforms</groupId>
<artifactId>common-datafilter</artifactId> <artifactId>common-datafilter</artifactId>

View File

@@ -96,6 +96,10 @@ public final class FlowApprovalType {
* 空审批人自动退回。 * 空审批人自动退回。
*/ */
public static final String EMPTY_USER_AUTO_REJECT = "empty_user_auto_reject"; public static final String EMPTY_USER_AUTO_REJECT = "empty_user_auto_reject";
/**
* 自动化任务。
*/
public static final String AUTO_FLOW_TASK = "auto_flow_task";
/** /**
* 私有构造函数,明确标识该常量类的作用。 * 私有构造函数,明确标识该常量类的作用。

View File

@@ -0,0 +1,69 @@
package com.orangeforms.common.flow.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 工作流自动化任务的动作类型。
*
* @author Jerry
* @date 2024-07-02
*/
public class FlowAutoActionType {
/**
* 添加新数据。
*/
public static final int ADD_NEW = 0;
/**
* 更新数据。
*/
public static final int UPDATE = 1;
/**
* 删除数据。
*/
public static final int DELETE = 2;
/**
* 查询单条数据。
*/
public static final int SELECT_ONE = 3;
/**
* 聚合计算。
*/
public static final int AGGREGATION_CALC = 5;
/**
* 数值计算。
*/
public static final int NUMBER_CALC = 6;
/**
* HTTP请求调用
*/
public static final int HTTP = 10;
private static final Map<Integer, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(ADD_NEW, "添加新数据");
DICT_MAP.put(UPDATE, "更新数据");
DICT_MAP.put(DELETE, "删除数据");
DICT_MAP.put(SELECT_ONE, "查询单条数据");
DICT_MAP.put(AGGREGATION_CALC, "聚合计算");
DICT_MAP.put(NUMBER_CALC, "数值计算");
DICT_MAP.put(HTTP, "HTTP请求调用");
}
/**
* 根据类型值返回显示值。
*
* @param flowActionType 类型值。
* @return 对应的显示名。
*/
public static String getShowNname(int flowActionType) {
return DICT_MAP.get(flowActionType);
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private FlowAutoActionType() {
}
}

View File

@@ -153,6 +153,11 @@ public class FlowConstant {
*/ */
public static final String GROUP_TYPE_DEPT_POST_LEADER = "DEPT_POST_LEADER"; public static final String GROUP_TYPE_DEPT_POST_LEADER = "DEPT_POST_LEADER";
/**
* 自动执行。
*/
public static final String GROUP_TYPE_AUTO_EXEC = "AUTO_EXEC";
/** /**
* 本部门岗位前缀。 * 本部门岗位前缀。
*/ */
@@ -258,6 +263,11 @@ public class FlowConstant {
*/ */
public static final String EMPTY_USER_TO_ASSIGNEE = "emptyUserToAssignee"; public static final String EMPTY_USER_TO_ASSIGNEE = "emptyUserToAssignee";
/**
* 自动化任务中用于传递的生产者日志对象变量。
*/
public static final String AUTO_FLOW_TRANS_PRODUCER_VAR = "transProducer";
/** /**
* 私有构造函数,明确标识该常量类的作用。 * 私有构造函数,明确标识该常量类的作用。
*/ */

View File

@@ -0,0 +1,276 @@
package com.orangeforms.common.flow.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.page.PageMethod;
import com.orangeforms.common.core.annotation.MyRequestBody;
import com.orangeforms.common.core.constant.ErrorCodeEnum;
import com.orangeforms.common.core.object.*;
import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.core.util.MyModelUtil;
import com.orangeforms.common.core.util.MyPageUtil;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.flow.dto.FlowDblinkDto;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.service.FlowDblinkService;
import com.orangeforms.common.flow.util.FlowDataSourceUtil;
import com.orangeforms.common.flow.vo.FlowDblinkVo;
import com.orangeforms.common.log.annotation.OperationLog;
import com.orangeforms.common.log.model.constant.SysOperationLogType;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 工作流数据库链接接口。
*
* @author Jerry
* @date 2024-07-02
*/
@Tag(name = "工作流数据库链接接口")
@Slf4j
@RestController
@RequestMapping("${common-flow.urlPrefix}/flowDblink")
@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true")
public class FlowDblinkController {
@Autowired
private FlowDblinkService flowDblinkService;
@Autowired
private FlowDataSourceUtil dataSourceUtil;
/**
* 新增数据库链接数据。
*
* @param flowDblinkDto 新增对象。
* @return 应答结果对象包含新增对象主键Id。
*/
@SaCheckPermission("flowDblink.all")
@OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/add")
public ResponseResult<Long> add(@MyRequestBody FlowDblinkDto flowDblinkDto) {
String errorMessage = MyCommonUtil.getModelValidationError(flowDblinkDto, false);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
FlowDblink flowDblink = MyModelUtil.copyTo(flowDblinkDto, FlowDblink.class);
flowDblink = flowDblinkService.saveNew(flowDblink);
return ResponseResult.success(flowDblink.getDblinkId());
}
/**
* 更新数据库链接数据。
*
* @param flowDblinkDto 更新对象。
* @return 应答结果对象。
*/
@SaCheckPermission("flowDblink.all")
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/update")
public ResponseResult<Void> update(@MyRequestBody FlowDblinkDto flowDblinkDto) {
String errorMessage = MyCommonUtil.getModelValidationError(flowDblinkDto, true);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
FlowDblink flowDblink = MyModelUtil.copyTo(flowDblinkDto, FlowDblink.class);
ResponseResult<FlowDblink> verifyResult = this.doVerifyAndGet(flowDblinkDto.getDblinkId());
if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult);
}
FlowDblink originalFlowDblink = verifyResult.getData();
if (ObjectUtil.notEqual(flowDblink.getDblinkType(), originalFlowDblink.getDblinkType())) {
errorMessage = "数据验证失败,不能修改数据库类型!";
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
}
String passwdKey = "password";
JSONObject configJson = JSON.parseObject(flowDblink.getConfiguration());
String password = configJson.getString(passwdKey);
if (StrUtil.isNotBlank(password) && StrUtil.isAllCharMatch(password, c -> '*' == c)) {
password = JSON.parseObject(originalFlowDblink.getConfiguration()).getString(passwdKey);
configJson.put(passwdKey, password);
flowDblink.setConfiguration(configJson.toJSONString());
}
if (!flowDblinkService.update(flowDblink, originalFlowDblink)) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success();
}
/**
* 删除数据库链接数据。
*
* @param dblinkId 删除对象主键Id。
* @return 应答结果对象。
*/
@SaCheckPermission("flowDblink.all")
@OperationLog(type = SysOperationLogType.DELETE)
@PostMapping("/delete")
public ResponseResult<Void> delete(@MyRequestBody Long dblinkId) {
String errorMessage;
// 验证关联Id的数据合法性
ResponseResult<FlowDblink> verifyResult = this.doVerifyAndGet(dblinkId);
if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult);
}
if (!flowDblinkService.remove(dblinkId)) {
errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
}
return ResponseResult.success();
}
/**
* 列出符合过滤条件的数据库链接列表。
*
* @param flowDblinkDtoFilter 过滤对象。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 应答结果对象,包含查询结果集。
*/
@SaCheckPermission("flowDblink.all")
@PostMapping("/list")
public ResponseResult<MyPageData<FlowDblinkVo>> list(
@MyRequestBody FlowDblinkDto flowDblinkDtoFilter,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
FlowDblink flowDblinkFilter = MyModelUtil.copyTo(flowDblinkDtoFilter, FlowDblink.class);
String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowDblink.class);
List<FlowDblink> flowDblinkList =
flowDblinkService.getFlowDblinkListWithRelation(flowDblinkFilter, orderBy);
for (FlowDblink dblink : flowDblinkList) {
this.maskOffPassword(dblink);
}
return ResponseResult.success(MyPageUtil.makeResponseData(flowDblinkList, FlowDblinkVo.class));
}
/**
* 查看指定数据库链接对象详情。
*
* @param dblinkId 指定对象主键Id。
* @return 应答结果对象,包含对象详情。
*/
@SaCheckPermission("flowDblink.all")
@GetMapping("/view")
public ResponseResult<FlowDblinkVo> view(@RequestParam Long dblinkId) {
ResponseResult<FlowDblink> verifyResult = this.doVerifyAndGet(dblinkId);
if (!verifyResult.isSuccess()) {
return ResponseResult.errorFrom(verifyResult);
}
FlowDblink flowDblink = verifyResult.getData();
flowDblinkService.buildRelationForData(flowDblink, MyRelationParam.full());
if (!StrUtil.equals(flowDblink.getAppCode(), TokenData.takeFromRequest().getAppCode())) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "数据验证失败,当前应用并不存在该数据库链接!");
}
this.maskOffPassword(flowDblink);
return ResponseResult.success(flowDblink, FlowDblinkVo.class);
}
/**
* 获取指定数据库链接下的所有动态表单依赖的数据表列表。
*
* @param dblinkId 数据库链接Id。
* @return 所有动态表单依赖的数据表列表
*/
@SaCheckPermission("flowDblink.all")
@GetMapping("/listDblinkTables")
public ResponseResult<List<SqlTable>> listDblinkTables(@RequestParam Long dblinkId) {
FlowDblink dblink = flowDblinkService.getById(dblinkId);
if (dblink == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success(flowDblinkService.getDblinkTableList(dblink));
}
/**
* 获取指定数据库链接下,指定数据表的所有字段信息。
*
* @param dblinkId 数据库链接Id。
* @param tableName 表名。
* @return 该表的所有字段列表。
*/
@SaCheckPermission("flowDblink.all")
@GetMapping("/listDblinkTableColumns")
public ResponseResult<List<SqlTableColumn>> listDblinkTableColumns(
@RequestParam Long dblinkId, @RequestParam String tableName) {
FlowDblink dblink = flowDblinkService.getById(dblinkId);
if (dblink == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success(flowDblinkService.getDblinkTableColumnList(dblink, tableName));
}
/**
* 测试数据库链接的接口。
*
* @return 应答结果。
*/
@GetMapping("/testConnection")
public ResponseResult<Void> testConnection(@RequestParam Long dblinkId) {
ResponseResult<FlowDblink> verifyAndGet = this.doVerifyAndGet(dblinkId);
if (!verifyAndGet.isSuccess()) {
return ResponseResult.errorFrom(verifyAndGet);
}
try {
dataSourceUtil.testConnection(dblinkId);
return ResponseResult.success();
} catch (Exception e) {
log.error("Failed to test connection with FLOW_DBLINK_ID [" + dblinkId + "]!", e);
return ResponseResult.error(ErrorCodeEnum.DATA_ACCESS_FAILED, "数据库连接失败!");
}
}
/**
* 以字典形式返回全部数据库链接数据集合。字典的键值为[dblinkId, dblinkName]。
* 白名单接口,登录用户均可访问。
*
* @param filter 过滤对象。
* @return 应答结果对象,包含的数据为 List<Map<String, String>>map中包含两条记录key的值分别是id和namevalue对应具体数据。
*/
@GetMapping("/listDict")
public ResponseResult<List<Map<String, Object>>> listDict(@ParameterObject FlowDblinkDto filter) {
List<FlowDblink> resultList =
flowDblinkService.getFlowDblinkList(MyModelUtil.copyTo(filter, FlowDblink.class), null);
return ResponseResult.success(
MyCommonUtil.toDictDataList(resultList, FlowDblink::getDblinkId, FlowDblink::getDblinkName));
}
private ResponseResult<FlowDblink> doVerifyAndGet(Long dblinkId) {
if (MyCommonUtil.existBlankArgument(dblinkId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
FlowDblink flowDblink = flowDblinkService.getById(dblinkId);
if (flowDblink == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
if (!StrUtil.equals(flowDblink.getAppCode(), TokenData.takeFromRequest().getAppCode())) {
return ResponseResult.error(
ErrorCodeEnum.DATA_VALIDATED_FAILED, "数据验证失败,当前应用并不存在该数据库链接!");
}
return ResponseResult.success(flowDblink);
}
private void maskOffPassword(FlowDblink dblink) {
String passwdKey = "password";
JSONObject configJson = JSON.parseObject(dblink.getConfiguration());
if (configJson.containsKey(passwdKey)) {
String password = configJson.getString(passwdKey);
if (StrUtil.isNotBlank(password)) {
configJson.put(passwdKey, StrUtil.repeat('*', password.length()));
dblink.setConfiguration(configJson.toJSONString());
}
}
}
}

View File

@@ -11,6 +11,7 @@ import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.annotation.DisableDataFilter; import com.orangeforms.common.core.annotation.DisableDataFilter;
import com.orangeforms.common.core.annotation.MyRequestBody; import com.orangeforms.common.core.annotation.MyRequestBody;
import com.orangeforms.common.core.constant.ErrorCodeEnum; import com.orangeforms.common.core.constant.ErrorCodeEnum;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.core.object.*; import com.orangeforms.common.core.object.*;
import com.orangeforms.common.core.util.MyModelUtil; import com.orangeforms.common.core.util.MyModelUtil;
import com.orangeforms.common.core.util.MyPageUtil; import com.orangeforms.common.core.util.MyPageUtil;
@@ -35,6 +36,7 @@ import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstance;
@@ -113,6 +115,26 @@ public class FlowOperationController {
return ResponseResult.success(); return ResponseResult.success();
} }
/**
* 发起一个自动化流程实例。
*
* @param processDefinitionKey 流程标识。
* @param variableData 变量数据。
* @return 应答结果对象。
*/
@SaCheckPermission("flowOperation.all")
@OperationLog(type = SysOperationLogType.START_FLOW)
@PostMapping("/startAuto")
public ResponseResult<String> startAuto(
@MyRequestBody(required = true) String processDefinitionKey, @MyRequestBody JSONObject variableData) {
try {
ProcessInstance processInstance = flowApiService.startAuto(processDefinitionKey, variableData);
return ResponseResult.success(processInstance.getProcessInstanceId());
} catch (MyRuntimeException e) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, e.getMessage());
}
}
/** /**
* 获取开始节点之后的第一个任务节点的数据。 * 获取开始节点之后的第一个任务节点的数据。
* *

View File

@@ -0,0 +1,13 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
/**
* 自动化流程变量访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowAutoVariableLogMapper extends BaseDaoMapper<FlowAutoVariableLog> {
}

View File

@@ -0,0 +1,26 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowDblink;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 数据库链接数据操作访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowDblinkMapper extends BaseDaoMapper<FlowDblink> {
/**
* 获取过滤后的对象列表。
*
* @param flowDblinkFilter 主表过滤对象。
* @param orderBy 排序字符串order by从句的参数。
* @return 对象列表。
*/
List<FlowDblink> getFlowDblinkList(
@Param("flowDblinkFilter") FlowDblink flowDblinkFilter, @Param("orderBy") String orderBy);
}

View File

@@ -0,0 +1,13 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowTransProducer;
/**
* 事务性业务数据生产者访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowTransProducerMapper extends BaseDaoMapper<FlowTransProducer> {
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orangeforms.common.flow.dao.FlowAutoVariableLogMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowAutoVariableLog">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="process_instance_id" jdbcType="VARCHAR" property="processInstanceId"/>
<result column="execution_id" jdbcType="VARCHAR" property="executionId"/>
<result column="task_id" jdbcType="VARCHAR" property="taskId"/>
<result column="task_key" jdbcType="VARCHAR" property="taskKey"/>
<result column="trace_id" jdbcType="VARCHAR" property="traceId"/>
<result column="variable_data" jdbcType="LONGVARCHAR" property="variableData"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orangeforms.common.flow.dao.FlowDblinkMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowDblink">
<id column="dblink_id" jdbcType="BIGINT" property="dblinkId"/>
<result column="app_code" jdbcType="VARCHAR" property="appCode"/>
<result column="dblink_name" jdbcType="VARCHAR" property="dblinkName"/>
<result column="dblink_description" jdbcType="VARCHAR" property="dblinkDescription"/>
<result column="configuration" jdbcType="VARCHAR" property="configuration"/>
<result column="dblink_type" jdbcType="INTEGER" property="dblinkType"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="create_user_id" jdbcType="BIGINT" property="createUserId"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="update_user_id" jdbcType="BIGINT" property="updateUserId"/>
</resultMap>
<!-- 如果有逻辑删除字段过滤,请写到这里 -->
<sql id="filterRef">
<!-- 这里必须加上全包名否则当filterRef被其他Mapper.xml包含引用的时候就会调用Mapper.xml中的该SQL片段 -->
<include refid="com.orangeforms.common.flow.dao.FlowDblinkMapper.inputFilterRef"/>
</sql>
<!-- 这里仅包含调用接口输入的主表过滤条件 -->
<sql id="inputFilterRef">
<if test="flowDblinkFilter != null">
<if test="flowDblinkFilter.appCode == null">
AND zz_flow_dblink.app_code IS NULL
</if>
<if test="flowDblinkFilter.appCode != null">
AND zz_flow_dblink.app_code = #{flowDblinkFilter.appCode}
</if>
<if test="flowDblinkFilter.dblinkType != null">
AND zz_flow_dblink.dblink_type = #{flowDblinkFilter.dblinkType}
</if>
</if>
</sql>
<select id="getFlowDblinkList" resultMap="BaseResultMap" parameterType="com.orangeforms.common.flow.model.FlowDblink">
SELECT * FROM zz_flow_dblink
<where>
<include refid="filterRef"/>
</where>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>
</select>
</mapper>

View File

@@ -14,6 +14,7 @@
<result column="bpmn_xml" jdbcType="LONGVARCHAR" property="bpmnXml"/> <result column="bpmn_xml" jdbcType="LONGVARCHAR" property="bpmnXml"/>
<result column="diagram_type" jdbcType="INTEGER" property="diagramType"/> <result column="diagram_type" jdbcType="INTEGER" property="diagramType"/>
<result column="bind_form_type" jdbcType="INTEGER" property="bindFormType"/> <result column="bind_form_type" jdbcType="INTEGER" property="bindFormType"/>
<result column="flow_type" jdbcType="INTEGER" property="flowType"/>
<result column="page_id" jdbcType="BIGINT" property="pageId"/> <result column="page_id" jdbcType="BIGINT" property="pageId"/>
<result column="default_form_id" jdbcType="BIGINT" property="defaultFormId"/> <result column="default_form_id" jdbcType="BIGINT" property="defaultFormId"/>
<result column="default_router_name" jdbcType="VARCHAR" property="defaultRouterName"/> <result column="default_router_name" jdbcType="VARCHAR" property="defaultRouterName"/>
@@ -58,6 +59,9 @@
<if test="flowEntryFilter.status != null"> <if test="flowEntryFilter.status != null">
AND zz_flow_entry.status = #{flowEntryFilter.status} AND zz_flow_entry.status = #{flowEntryFilter.status}
</if> </if>
<if test="flowEntryFilter.flowType != null">
AND zz_flow_entry.flow_type = #{flowEntryFilter.flowType}
</if>
</if> </if>
</sql> </sql>
@@ -73,6 +77,7 @@
status, status,
diagram_type, diagram_type,
bind_form_type, bind_form_type,
flow_type,
page_id, page_id,
default_form_id, default_form_id,
default_router_name, default_router_name,

View File

@@ -14,6 +14,7 @@
<result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/> <result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/>
<result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/> <result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/>
<result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/> <result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/>
<result column="auto_config_json" jdbcType="VARCHAR" property="autoConfigJson"/>
</resultMap> </resultMap>
<insert id="insertList"> <insert id="insertList">
@@ -30,7 +31,8 @@
#{item.deptIds}, #{item.deptIds},
#{item.candidateUsernames}, #{item.candidateUsernames},
#{item.copyListJson}, #{item.copyListJson},
#{item.extraDataJson}) #{item.extraDataJson},
#{item.autoConfigJson})
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orangeforms.common.flow.dao.FlowTransProducerMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowTransProducer">
<id column="trans_id" jdbcType="BIGINT" property="transId"/>
<result column="app_code" jdbcType="VARCHAR" property="appCode"/>
<result column="dblink_id" jdbcType="BIGINT" property="dblinkId"/>
<result column="process_instance_id" jdbcType="VARCHAR" property="processInstanceId"/>
<result column="execution_id" jdbcType="VARCHAR" property="executionId"/>
<result column="task_id" jdbcType="VARCHAR" property="taskId"/>
<result column="task_key" jdbcType="VARCHAR" property="taskKey"/>
<result column="task_name" jdbcType="VARCHAR" property="taskName"/>
<result column="task_comment" jdbcType="VARCHAR" property="taskComment"/>
<result column="url" jdbcType="VARCHAR" property="url"/>
<result column="init_method" jdbcType="VARCHAR" property="initMethod"/>
<result column="trace_id" jdbcType="VARCHAR" property="traceId"/>
<result column="sql_data" jdbcType="LONGVARCHAR" property="sqlData"/>
<result column="auto_task_config" jdbcType="LONGVARCHAR" property="autoTaskConfig"/>
<result column="try_times" jdbcType="INTEGER" property="tryTimes"/>
<result column="error_reason" jdbcType="LONGVARCHAR" property="errorReason"/>
<result column="create_login_name" jdbcType="VARCHAR" property="createUsername"/>
<result column="create_username" jdbcType="VARCHAR" property="createLoginName"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,53 @@
package com.orangeforms.common.flow.dto;
import com.orangeforms.common.core.validator.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
/**
* 工作流自动化流程数据表所在数据库链接Dto对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Schema(description = "工作流自动化流程数据表所在数据库链接Dto对象")
@Data
public class FlowDblinkDto {
/**
* 主键Id。
*/
@Schema(description = "主键Id")
@NotNull(message = "数据验证失败主键Id不能为空", groups = {UpdateGroup.class})
private Long dblinkId;
/**
* 链接中文名称。
*/
@Schema(description = "链接中文名称")
@NotBlank(message = "数据验证失败,链接中文名称不能为空!")
private String dblinkName;
/**
* 链接描述。
*/
@Schema(description = "链接中文名称")
private String dblinkDescription;
/**
* 配置信息。
*/
@Schema(description = "配置信息")
@NotBlank(message = "数据验证失败,配置信息不能为空!")
private String configuration;
/**
* 数据库链接类型。
*/
@Schema(description = "数据库链接类型")
@NotNull(message = "数据验证失败,数据库链接类型不能为空!")
private Integer dblinkType;
}

View File

@@ -0,0 +1,86 @@
package com.orangeforms.common.flow.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.orangeforms.common.core.util.ApplicationContextHolder;
import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowAutoActionType;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.model.FlowTaskComment;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.object.AutoTaskConfig;
import com.orangeforms.common.flow.service.FlowApiService;
import com.orangeforms.common.flow.service.FlowTaskCommentService;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import com.orangeforms.common.flow.util.AutoFlowHelper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 空审批人审批人检测监听器。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
public class ReceiveTaskEndListener implements ExecutionListener {
private final transient FlowApiService flowApiService =
ApplicationContextHolder.getBean(FlowApiService.class);
private final transient FlowTransProducerService flowTransProducerService =
ApplicationContextHolder.getBean(FlowTransProducerService.class);
private final transient FlowTaskCommentService flowTaskCommentService =
ApplicationContextHolder.getBean(FlowTaskCommentService.class);
private final transient AutoFlowHelper autoFlowHelper =
ApplicationContextHolder.getBean(AutoFlowHelper.class);
@Override
public void notify(DelegateExecution d) {
//先从内存中的临时变量中获取,以便提升正常运行情况下的运行时效率。
FlowTransProducer transProducer =
(FlowTransProducer) d.getTransientVariable(FlowConstant.AUTO_FLOW_TRANS_PRODUCER_VAR);
//如果临时变量中没有存在,则从流程执行实例中查询该变量。
if (transProducer == null) {
transProducer = (FlowTransProducer)
flowApiService.getExecutionVariable(d.getId(), FlowConstant.AUTO_FLOW_TRANS_PRODUCER_VAR);
}
if (transProducer == null) {
FlowTransProducer filter = new FlowTransProducer();
filter.setProcessInstanceId(d.getProcessInstanceId());
filter.setExecutionId(d.getId());
filter.setTaskKey(d.getCurrentActivityId());
List<FlowTransProducer> transProducers = flowTransProducerService.getListByFilter(filter);
if (CollUtil.isNotEmpty(transProducers)) {
transProducers = transProducers.stream()
.sorted(Comparator.comparing(FlowTransProducer::getTransId, Comparator.reverseOrder()))
.collect(Collectors.toList());
transProducer = transProducers.get(0);
}
}
if (transProducer == null) {
return;
}
if (StrUtil.isNotBlank(transProducer.getAutoTaskConfig())) {
AutoTaskConfig taskConfig = JSON.parseObject(transProducer.getAutoTaskConfig(), AutoTaskConfig.class);
autoFlowHelper.executeTask(transProducer.getTransId(), taskConfig, d);
FlowTaskComment comment = new FlowTaskComment();
comment.setTaskKey(d.getCurrentActivityId());
comment.setTaskName(taskConfig.getTaskName());
comment.setProcessInstanceId(d.getProcessInstanceId());
comment.setExecutionId(d.getId());
comment.setApprovalType(FlowApprovalType.AUTO_FLOW_TASK);
String s = StrFormatter.format("执行任务 [{}] 成功,任务类型 [{}]",
taskConfig.getTaskName(), FlowAutoActionType.getShowNname(taskConfig.getActionType()));
comment.setTaskComment(s);
flowTaskCommentService.saveNew(comment);
}
flowTransProducerService.removeById(transProducer.getTransId());
}
}

View File

@@ -0,0 +1,46 @@
package com.orangeforms.common.flow.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.orangeforms.common.core.util.ApplicationContextHolder;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.object.AutoTaskConfig;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import com.orangeforms.common.flow.util.AutoFlowHelper;
import com.orangeforms.common.flow.util.ListenerEventPublishHelper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
/**
* 空审批人审批人检测监听器。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
public class ReceiveTaskStartListener implements ExecutionListener {
private final transient AutoFlowHelper autoFlowHelper =
ApplicationContextHolder.getBean(AutoFlowHelper.class);
private final transient FlowTransProducerService flowTransProducerService =
ApplicationContextHolder.getBean(FlowTransProducerService.class);
private final transient ListenerEventPublishHelper eventPublishHelper =
ApplicationContextHolder.getBean(ListenerEventPublishHelper.class);
@Override
public void notify(DelegateExecution d) {
//TODO 在目标表所在数据库也要创建业务执行的流水表基于TransProduer的TransId做唯一性校验。
AutoTaskConfig taskConfig = autoFlowHelper.parseAutoTaskConfig(d.getProcessDefinitionId(), d.getCurrentActivityId());
FlowTransProducer producerData = new FlowTransProducer();
producerData.setProcessInstanceId(d.getProcessInstanceId());
producerData.setExecutionId(d.getId());
producerData.setTaskKey(d.getCurrentActivityId());
producerData.setDblinkId(taskConfig.getDestDblinkId());
producerData.setInitMethod("ReceiveTaskStartListener.notify");
producerData.setTryTimes(1);
producerData.setAutoTaskConfig(JSON.toJSONString(taskConfig, SerializerFeature.WriteDateUseDateFormat));
flowTransProducerService.saveNew(producerData);
eventPublishHelper.publishEvent(producerData);
}
}

View File

@@ -0,0 +1,67 @@
package com.orangeforms.common.flow.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 自动化流程变量实体。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@TableName(value = "zz_flow_auto_variable_log")
public class FlowAutoVariableLog {
/**
* 主键Id。
*/
@TableId(value = "id")
private Long id;
/**
* 流程实例Id。
*/
@TableField(value = "process_instance_id")
private String processInstanceId;
/**
* 执行实例Id。
*/
@TableField(value = "execution_id")
private String executionId;
/**
* 任务Id。
*/
@TableField(value = "task_id")
private String taskId;
/**
* 任务标识。
*/
@TableField(value = "task_key")
private String taskKey;
/**
* 当前请求的traceId。
*/
@TableField(value = "trace_id")
private String traceId;
/**
* 变量数据。
*/
@TableField(value = "variable_data")
private String variableData;
/**
* 创建时间。
*/
@TableField(value = "create_time")
private Date createTime;
}

View File

@@ -0,0 +1,87 @@
package com.orangeforms.common.flow.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.orangeforms.common.core.annotation.RelationConstDict;
import com.orangeforms.common.dbutil.constant.DblinkType;
import lombok.Data;
import java.util.Date;
import java.util.Map;
/**
* 在线表单数据表所在数据库链接实体对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@TableName(value = "zz_flow_dblink")
public class FlowDblink {
/**
* 主键Id。
*/
@TableId(value = "dblink_id")
private Long dblinkId;
/**
* 应用编码。为空时,表示非第三方应用接入。
*/
@TableField(value = "app_code")
private String appCode;
/**
* 链接中文名称。
*/
@TableField(value = "dblink_name")
private String dblinkName;
/**
* 链接描述。
*/
@TableField(value = "dblink_description")
private String dblinkDescription;
/**
* 配置信息。
*/
private String configuration;
/**
* 数据库链接类型。
*/
@TableField(value = "dblink_type")
private Integer dblinkType;
/**
* 创建时间。
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 创建者。
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 修改时间。
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 更新者。
*/
@TableField(value = "update_user_id")
private Long updateUserId;
@RelationConstDict(
masterIdField = "dblinkType",
constantDictClass = DblinkType.class)
@TableField(exist = false)
private Map<String, Object> dblinkTypeDictMap;
}

View File

@@ -88,6 +88,12 @@ public class FlowEntry {
@TableField(value = "bind_form_type") @TableField(value = "bind_form_type")
private Integer bindFormType; private Integer bindFormType;
/**
* 流程类型。
*/
@TableField(value = "flow_type")
private Integer flowType;
/** /**
* 在线表单的页面Id。 * 在线表单的页面Id。
*/ */

View File

@@ -1,6 +1,7 @@
package com.orangeforms.common.flow.model; package com.orangeforms.common.flow.model;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.orangeforms.common.flow.model.constant.FlowVariableType;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
@@ -74,4 +75,13 @@ public class FlowEntryVariable {
*/ */
@TableField(value = "create_time") @TableField(value = "create_time")
private Date createTime; private Date createTime;
public static FlowEntryVariable createSystemVariable(String variableName, String showName) {
FlowEntryVariable variable = new FlowEntryVariable();
variable.variableName = variableName;
variable.showName = showName;
variable.variableType = FlowVariableType.SYSTEM;
variable.builtin = true;
return variable;
}
} }

View File

@@ -84,4 +84,10 @@ public class FlowTaskExt {
*/ */
@TableField(value = "extra_data_json") @TableField(value = "extra_data_json")
private String extraDataJson; private String extraDataJson;
/**
* 自动化任务配置数据存储为JSON的字符串格式。
*/
@TableField(value = "auto_config_json")
private String autoConfigJson;
} }

View File

@@ -0,0 +1,133 @@
package com.orangeforms.common.flow.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 流程处理事务事件生产者流水实体。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@TableName(value = "zz_flow_trans_producer")
public class FlowTransProducer {
/**
* 流水Id。
*/
@TableId(value = "trans_id")
private Long transId;
/**
* 应用编码。为空时,表示非第三方应用接入。
*/
@TableField(value = "app_code")
private String appCode;
/**
* 业务数据库链接Id。
*/
@TableField(value = "dblink_id")
private Long dblinkId;
/**
* 流程实例Id。
*/
@TableField(value = "process_instance_id")
private String processInstanceId;
/**
* 执行实例Id。
*/
@TableField(value = "execution_id")
private String executionId;
/**
* 任务Id。
*/
@TableField(value = "task_id")
private String taskId;
/**
* 任务标识。
*/
@TableField(value = "task_key")
private String taskKey;
/**
* 任务名称。
*/
@TableField(value = "task_name")
private String taskName;
/**
* 审批批注。
*/
@TableField(value = "task_comment")
private String taskComment;
/**
* 当前请求的url。
*/
@TableField(value = "url")
private String url;
/**
* 创建该事务性事件对象的初始方法。格式为:方法名(参数类型1,参数类型2)。
*/
@TableField(value = "init_method")
private String initMethod;
/**
* 当前请求的traceId。
*/
@TableField(value = "trace_id")
private String traceId;
/**
* 和SQL操作相关的数据。值类型为TransactionalBusinessData.BusinessSqlData对象。
*/
@TableField(value = "sql_data")
private String sqlData;
/**
* 自动化流程的任务配置。
*/
@TableField(value = "auto_task_config")
private String autoTaskConfig;
/**
* 尝试次数。默认的插入值为1。
*/
@TableField(value = "try_times")
private Integer tryTimes;
/**
* 提交业务数据时的错误信息。如果正常提交,该值为空。
*/
@TableField(value = "error_reason")
private String errorReason;
/**
* 创建者登录名。
*/
@TableField(value = "create_login_name")
private String createLoginName;
/**
* 创建者中文用户名。
*/
@TableField(value = "create_username")
private String createUsername;
/**
* 创建时间。
*/
@TableField(value = "create_time")
private Date createTime;
}

View File

@@ -0,0 +1,44 @@
package com.orangeforms.common.flow.model.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 流程类型。
*
* @author Jerry
* @date 2024-07-02
*/
public final class FlowEntryType {
/**
* 普通审批。
*/
public static final int NORMAL_TYPE = 0;
/**
* 自动化流程。
*/
public static final int AUTO_TYPE = 1;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(NORMAL_TYPE, "普通审批");
DICT_MAP.put(AUTO_TYPE, "自动化流程");
}
/**
* 判断参数是否为当前常量字典的合法值。
*
* @param value 待验证的参数值。
* @return 合法返回true否则false。
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private FlowEntryType() {
}
}

View File

@@ -19,11 +19,21 @@ public final class FlowVariableType {
* 任务变量。 * 任务变量。
*/ */
public static final int TASK = 1; public static final int TASK = 1;
/**
* 系统内置变量。
*/
public static final int SYSTEM = 2;
/**
* 自定义变量。
*/
public static final int CUSTOM = 4;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2); private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static { static {
DICT_MAP.put(INSTANCE, "流程实例变量"); DICT_MAP.put(INSTANCE, "流程实例变量");
DICT_MAP.put(TASK, "任务变量"); DICT_MAP.put(TASK, "任务变量");
DICT_MAP.put(SYSTEM, "系统内置变量");
DICT_MAP.put(CUSTOM, "自定义变量");
} }
/** /**

View File

@@ -0,0 +1,28 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务的执行数据。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoExecData {
/**
* 数据库链接ID。
*/
private Long dblinkId;
/**
* 执行sql。
*/
private String sql;
/**
* sql参数列表。
*/
private List<Object> params;
}

View File

@@ -0,0 +1,53 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务调用HTTP请求的对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoHttpRequestInfo {
public static final String BODY_TYPE_FORMDATA = "formData";
public static final String BODY_TYPE_RAW = "raw";
public static final String RAW_TYPE_TEXT = "text";
public static final String RAW_TYPE_JSON = "json";
/**
* 请求地址。
*/
private String url;
/**
* POST/GET/PUT ...
*/
private String httpMethod;
/**
* 请求body的类型。
*/
private String bodyType;
/**
* 仅当bodyType为raw的时候可用。
*/
private String rawType;
/**
* 当bodyType为raw的时候请求体的数据。
*/
private String bodyData;
/**
* HTTP请求头列表。
*/
private List<AutoTaskConfig.ValueInfo> headerList;
/**
* 仅当bodyType为formData时可用。
*/
private List<AutoTaskConfig.ValueInfo> formDataList;
/**
* url参数。
*/
private List<AutoTaskConfig.ValueInfo> urlParamList;
}

View File

@@ -0,0 +1,38 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
/**
* 自动化任务调用HTTP的应答对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoHttpResponseData {
public static final String STOP_ON_FAIL = "stop";
public static final String CONTINUE_ON_FAIL = "continue";
/**
* 应答成功的HTTP状态码多个状态码之间逗号分隔。
*/
private String successStatusCode;
/**
* 如果请求状态码为成功,还需要进一步根据应答体中的指定字段,进一步判断是否成功。
* 如data.isSuccess。
*/
private String successBodyField = "success";
/**
* 请求体中错误信息字段。
*/
private String errorMessageBodyField = "errorMessage";
/**
* 失败处理类型。
*/
private String failHandleType;
/**
* HTTP请求的应答体定义。
*/
private String httpResponseBody;
}

View File

@@ -0,0 +1,180 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务的执行配置数据。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoTaskConfig {
public static final String SRC_FILTER_SQL = "sql";
public static final String SRC_FILTER_FIELD = "field";
/**
* 固定值。
*/
public static final int FIXED_VALUE = 0;
/**
* 表字段值。
*/
public static final int COLUMN_VALUE = 1;
/**
* 流程变量。
*/
public static final int VARIABLE_VALUE = 2;
/**
* 任务标识,同时也是当前任务的输出变量名。
*/
private String taskKey;
/**
* 任务名称。
*/
private String taskName;
/**
* 执行动作的类型。
*/
private Integer actionType;
/**
* 数据库链接Id。
*/
private Long srcDblinkId;
/**
* 源数据库类型。
*/
private Integer srcDblinkType;
/**
* 源表名称。
*/
private String srcTableName;
/**
* 源数据的过滤类型可能值为sql/field。
*/
private String srcFilterType;
/**
* 源数据过滤条件列表。
*/
private List<FilterInfo> srcFilterList;
/**
* 源数据过滤sql。
*/
private String srcFilterSql;
/**
* 目标数据库链接Id。
*/
private Long destDblinkId;
/**
* 目标数据库类型。
*/
private Integer destDblinkType;
/**
* 目标表名称。
*/
private String destTableName;
/**
* 目标数据的过滤类型可能值为sql/field。
*/
private String destFilterType;
/**
* 目标数据过滤条件列表。
*/
private List<FilterInfo> destFilterList;
/**
* 目标数据过滤sql。
*/
private String destFilterSql;
/**
* 逻辑删除字段。
*/
private String logicDeleteField;
/**
* 数据插入对象
*/
private List<ValueInfo> insertDataList;
/**
* 数据插入对象
*/
private List<ValueInfo> updateDataList;
/**
* SELECT查询的字段。
*/
private List<String> selectFieldList;
/**
* 聚合数据列表。
*/
private List<AggregationInfo> aggregationDataList;
/**
* 数值计算表达式。
*/
private String calculateFormula;
/**
* HTTP请求信息。
*/
private AutoHttpRequestInfo httpRequestInfo;
/**
* HTTP应答数据。
*/
private AutoHttpResponseData httpResponnseData;
@Data
public static class ValueInfo {
/**
* HTTP任务中使用。
*/
private String key;
/**
* 目标字段名。
*/
private String destColumnName;
/**
* 值的类型,参考当前对象的常量。
*/
private Integer type;
/**
* 值。
*/
private String srcValue;
}
@Data
public static class FilterInfo {
/**
* 过滤字段名。
*/
private String filterColumnName;
/**
* 过滤类型。
*/
private Integer filterType;
/**
* 过滤值类型。
*/
private Integer valueType;
/**
* 过滤值。
*/
private String filterValue;
}
@Data
public static class AggregationInfo {
/**
* 聚合函数如SUM/COUNT/AVERAGE/MIN/MAX。
*/
private String aggregationFunction;
/**
* 聚合字段。
*/
private String aggregationColumn;
/**
* 别名。
*/
private String alias;
}
}

View File

@@ -0,0 +1,43 @@
package com.orangeforms.common.flow.object;
import lombok.Data;
import java.util.List;
/**
* 自动化任务的变量对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
public class AutoTaskVariable {
/**
* 流程任务定义。
*/
private String taskKey;
/**
* 流程名称。
*/
private String taskName;
/**
* 执行动作的类型。
*/
private Integer actionType;
/**
* 输出变量名。
*/
private String outputVariableName;
/**
* 输出变量的显示名。
*/
private String outputVariableShowName;
/**
* 选择的字段列表。
*/
private List<String> fieldList;
/**
* HTTP请求的应答体定义。
*/
private String httpResponseBody;
}

View File

@@ -22,7 +22,6 @@ import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstance;
import javax.xml.stream.XMLStreamException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -46,6 +45,15 @@ public interface FlowApiService {
*/ */
ProcessInstance start(String processDefinitionId, Object dataId); ProcessInstance start(String processDefinitionId, Object dataId);
/**
* 启动自动化流程实例。
*
* @param processDefinitionKey 流程定义标识。
* @param variableData 变量参数数据。
* @return 新启动的流程实例。
*/
ProcessInstance startAuto(String processDefinitionKey, JSONObject variableData);
/** /**
* 完成第一个用户任务。 * 完成第一个用户任务。
* *
@@ -515,9 +523,8 @@ public interface FlowApiService {
* *
* @param bpmnXml xml格式的流程模型字符串。 * @param bpmnXml xml格式的流程模型字符串。
* @return 转换后的标准的流程模型。 * @return 转换后的标准的流程模型。
* @throws XMLStreamException XML流处理异常
*/ */
BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException; BpmnModel convertToBpmnModel(String bpmnXml);
/** /**
* 回退到上一个用户任务节点。如果没有指定,则回退到上一个任务。 * 回退到上一个用户任务节点。如果没有指定,则回退到上一个任务。
@@ -558,6 +565,14 @@ public interface FlowApiService {
Tuple2<Set<String>, Set<String>> getDeptPostIdAndPostIds( Tuple2<Set<String>, Set<String>> getDeptPostIdAndPostIds(
FlowTaskExt flowTaskExt, String processInstanceId, boolean historic); FlowTaskExt flowTaskExt, String processInstanceId, boolean historic);
/**
* 获取指定流程实例中正在运行是的任务Id列表。
*
* @param processInstanceId 流程实例Id。
* @return 指定流程实例中正在运行是的任务Id列表。
*/
List<String> getCurrentActivityIds(String processInstanceId);
/** /**
* 获取流程图中所有用户任务的映射。 * 获取流程图中所有用户任务的映射。
* *

View File

@@ -0,0 +1,28 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
/**
* 自动化流程变量操作服务接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowAutoVariableLogService extends IBaseService<FlowAutoVariableLog, Long> {
/**
* 保存数据对象。
*
* @param flowAutoVariableLog 数据对象。
*/
void saveNew(FlowAutoVariableLog flowAutoVariableLog);
/**
* 获取指定自动化流程实例的最新变量对象。
*
* @param processInstanceId 流程实例Id。
* @return 自动化流程实例的最新变量对象。
*/
FlowAutoVariableLog getAutoVariableByProcessInstanceId(String processInstanceId);
}

View File

@@ -0,0 +1,89 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.flow.model.FlowDblink;
import java.util.List;
/**
* 数据库链接数据操作服务接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowDblinkService extends IBaseService<FlowDblink, Long> {
/**
* 保存新增对象。
*
* @param flowDblink 新增对象。
* @return 返回新增对象。
*/
FlowDblink saveNew(FlowDblink flowDblink);
/**
* 更新数据对象。
*
* @param flowDblink 更新的对象。
* @param originalFlowDblink 原有数据对象。
* @return 成功返回true否则false。
*/
boolean update(FlowDblink flowDblink, FlowDblink originalFlowDblink);
/**
* 删除指定数据。
*
* @param dblinkId 主键Id。
* @return 成功返回true否则false。
*/
boolean remove(Long dblinkId);
/**
* 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。
* 如果需要同时获取关联数据,请移步(getOnlineDblinkListWithRelation)方法。
*
* @param filter 过滤对象。
* @param orderBy 排序参数。
* @return 查询结果集。
*/
List<FlowDblink> getFlowDblinkList(FlowDblink filter, String orderBy);
/**
* 获取主表的查询结果,以及主表关联的字典数据和一对一从表数据,以及一对一从表的字典数据。
* 该查询会涉及到一对一从表的关联过滤,或一对多从表的嵌套关联过滤,因此性能不如单表过滤。
* 如果仅仅需要获取主表数据,请移步(getOnlineDblinkList),以便获取更好的查询性能。
*
* @param filter 主表过滤对象。
* @param orderBy 排序参数。
* @return 查询结果集。
*/
List<FlowDblink> getFlowDblinkListWithRelation(FlowDblink filter, String orderBy);
/**
* 获取指定DBLink下面的全部数据表。
*
* @param dblink 数据库链接对象。
* @return 全部数据表列表。
*/
List<SqlTable> getDblinkTableList(FlowDblink dblink);
/**
* 获取指定DBLink下指定表名的数据表对象及其关联字段列表。
*
* @param dblink 数据库链接对象。
* @param tableName 数据库中的数据表名。
* @return 数据表对象。
*/
SqlTable getDblinkTable(FlowDblink dblink, String tableName);
/**
* 获取指定DBLink下指定表名的字段列表。
*
* @param dblink 数据库链接对象。
* @param tableName 数据库中的数据表名。
* @return 表的字段列表。
*/
List<SqlTableColumn> getDblinkTableColumnList(FlowDblink dblink, String tableName);
}

View File

@@ -76,6 +76,13 @@ public interface FlowTaskExtService extends IBaseService<FlowTaskExt, String> {
String executionId, String executionId,
FlowTaskExt flowTaskExt); FlowTaskExt flowTaskExt);
/**
* 验证自动化任务的配置如果有问题直接抛出MyRuntimeException异常。
*
* @param taskExt 流程任务的扩展。
*/
void verifyAutoTaskConfig(FlowTaskExt taskExt);
/** /**
* 通过UserTask对象中的扩展节点信息构建FLowTaskExt对象。 * 通过UserTask对象中的扩展节点信息构建FLowTaskExt对象。
* *

View File

@@ -0,0 +1,21 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.flow.model.FlowTransProducer;
/**
* 流程引擎审批操作的生产者流水服务接口。
*
* @author Jerry
* @date 2024-07-02
*/
public interface FlowTransProducerService extends IBaseService<FlowTransProducer, Long> {
/**
* 保存新数据。
*
* @param data 数据对象。
* @return 更新后的对象。
*/
FlowTransProducer saveNew(FlowTransProducer data);
}

View File

@@ -25,14 +25,13 @@ import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.constant.FlowTaskStatus; import com.orangeforms.common.flow.constant.FlowTaskStatus;
import com.orangeforms.common.flow.exception.FlowOperationException; import com.orangeforms.common.flow.exception.FlowOperationException;
import com.orangeforms.common.flow.model.*; import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.model.constant.FlowEntryType;
import com.orangeforms.common.flow.object.FlowEntryExtensionData; import com.orangeforms.common.flow.object.FlowEntryExtensionData;
import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign; import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign;
import com.orangeforms.common.flow.object.FlowTaskOperation; import com.orangeforms.common.flow.object.FlowTaskOperation;
import com.orangeforms.common.flow.object.FlowTaskPostCandidateGroup; import com.orangeforms.common.flow.object.FlowTaskPostCandidateGroup;
import com.orangeforms.common.flow.service.*; import com.orangeforms.common.flow.service.*;
import com.orangeforms.common.flow.util.BaseFlowIdentityExtHelper; import com.orangeforms.common.flow.util.*;
import com.orangeforms.common.flow.util.CustomChangeActivityStateBuilderImpl;
import com.orangeforms.common.flow.util.FlowCustomExtFactory;
import com.orangeforms.common.flow.vo.FlowTaskVo; import com.orangeforms.common.flow.vo.FlowTaskVo;
import com.orangeforms.common.flow.vo.FlowUserInfoVo; import com.orangeforms.common.flow.vo.FlowUserInfoVo;
import lombok.Cleanup; import lombok.Cleanup;
@@ -114,6 +113,14 @@ public class FlowApiServiceImpl implements FlowApiService {
private FlowCustomExtFactory flowCustomExtFactory; private FlowCustomExtFactory flowCustomExtFactory;
@Autowired @Autowired
private FlowMultiInstanceTransService flowMultiInstanceTransService; private FlowMultiInstanceTransService flowMultiInstanceTransService;
@Autowired
private FlowTransProducerService flowTransProducerService;
@Autowired
private FlowAutoVariableLogService flowAutoVariableLogService;
@Autowired
private FlowOperationHelper flowOperationHelper;
@Autowired
private AutoFlowHelper autoFlowHelper;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
@@ -134,6 +141,45 @@ public class FlowApiServiceImpl implements FlowApiService {
return builder.start(); return builder.start();
} }
@Transactional(rollbackFor = Exception.class)
@Override
public ProcessInstance startAuto(String processDefinitionKey, JSONObject variableData) {
ResponseResult<FlowEntry> flowEntryResult = flowOperationHelper.verifyAndGetFlowEntry(processDefinitionKey);
if (!flowEntryResult.isSuccess()) {
throw new MyRuntimeException(flowEntryResult.getErrorMessage());
}
FlowEntry flowEntry = flowEntryResult.getData();
if (!flowEntry.getFlowType().equals(FlowEntryType.AUTO_TYPE)) {
throw new MyRuntimeException("数据验证失败,该接口只能启动自动化流程!");
}
JSONObject systemVariables = autoFlowHelper.getNonRealtimeSystemVariables();
if (variableData == null) {
variableData = new JSONObject();
}
variableData.putAll(systemVariables);
AutoFlowHelper.setStartAutoInitVariables(variableData);
TokenData tokenData = TokenData.takeFromRequest();
Authentication.setAuthenticatedUserId(tokenData.getLoginName());
String businessKey = variableData.getString("businessKey");
String processDefinitionId = flowEntry.getMainFlowEntryPublish().getProcessDefinitionId();
ProcessInstanceBuilder builder =
runtimeService.createProcessInstanceBuilder()
.processDefinitionId(processDefinitionId).businessKey(businessKey);
if (tokenData.getTenantId() != null) {
builder.tenantId(tokenData.getTenantId().toString());
} else {
if (tokenData.getAppCode() != null) {
builder.tenantId(tokenData.getAppCode());
}
}
ProcessInstance instance = builder.start();
FlowAutoVariableLog data = new FlowAutoVariableLog();
data.setProcessInstanceId(instance.getProcessInstanceId());
data.setVariableData(variableData.toJSONString());
flowAutoVariableLogService.saveNew(data);
return instance;
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public Task takeFirstTask(String processInstanceId, FlowTaskComment flowTaskComment, JSONObject taskVariableData) { public Task takeFirstTask(String processInstanceId, FlowTaskComment flowTaskComment, JSONObject taskVariableData) {
@@ -983,6 +1029,12 @@ public class FlowApiServiceImpl implements FlowApiService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public CallResult stopProcessInstance(String processInstanceId, String stopReason, int status) { public CallResult stopProcessInstance(String processInstanceId, String stopReason, int status) {
ProcessInstance instance = this.getProcessInstance(processInstanceId);
FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(instance.getProcessDefinitionKey());
if (flowEntry.getFlowType().equals(FlowEntryType.AUTO_TYPE)) {
runtimeService.deleteProcessInstance(processInstanceId, stopReason);
return CallResult.ok();
}
List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list(); List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list();
if (CollUtil.isEmpty(taskList)) { if (CollUtil.isEmpty(taskList)) {
return CallResult.error("数据验证失败,当前流程尚未开始或已经结束!"); return CallResult.error("数据验证失败,当前流程尚未开始或已经结束!");
@@ -1092,11 +1144,15 @@ public class FlowApiServiceImpl implements FlowApiService {
} }
@Override @Override
public BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException { public BpmnModel convertToBpmnModel(String bpmnXml) {
BpmnXMLConverter converter = new BpmnXMLConverter(); try {
InputStream in = new ByteArrayInputStream(bpmnXml.getBytes(StandardCharsets.UTF_8)); BpmnXMLConverter converter = new BpmnXMLConverter();
@Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(in); InputStream in = new ByteArrayInputStream(bpmnXml.getBytes(StandardCharsets.UTF_8));
return converter.convertToBpmnModel(reader); @Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(in);
return converter.convertToBpmnModel(reader);
} catch (XMLStreamException e) {
throw new MyRuntimeException(e);
}
} }
@Transactional @Transactional
@@ -1722,6 +1778,15 @@ public class FlowApiServiceImpl implements FlowApiService {
return new Tuple2<>(deptPostIdSet, postIdSet); return new Tuple2<>(deptPostIdSet, postIdSet);
} }
@Override
public List<String> getCurrentActivityIds(String processInstanceId) {
List<Execution> runExecutionList =
runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list();
return runExecutionList.stream()
.map(Execution::getActivityId)
.filter(StrUtil::isNotBlank).collect(Collectors.toList());
}
@Override @Override
public Map<String, UserTask> getAllUserTaskMap(String processDefinitionId) { public Map<String, UserTask> getAllUserTaskMap(String processDefinitionId) {
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

View File

@@ -0,0 +1,53 @@
package com.orangeforms.common.flow.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.flow.dao.FlowAutoVariableLogMapper;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
import com.orangeforms.common.flow.service.FlowAutoVariableLogService;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("flowAutoVariableLogService")
public class FlowAutoVariableLogServiceImpl extends BaseService<FlowAutoVariableLog, Long> implements FlowAutoVariableLogService {
@Autowired
private FlowAutoVariableLogMapper flowAutoVariableLogMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Override
protected BaseDaoMapper<FlowAutoVariableLog> mapper() {
return flowAutoVariableLogMapper;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveNew(FlowAutoVariableLog o) {
o.setId(idGenerator.nextLongId());
o.setTraceId(MyCommonUtil.getTraceId());
o.setCreateTime(new Date());
flowAutoVariableLogMapper.insert(o);
}
@Override
public FlowAutoVariableLog getAutoVariableByProcessInstanceId(String processInstanceId) {
LambdaQueryWrapper<FlowAutoVariableLog> qw = new LambdaQueryWrapper<>();
qw.eq(FlowAutoVariableLog::getProcessInstanceId, processInstanceId);
return flowAutoVariableLogMapper.selectOne(qw);
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@Slf4j @Slf4j
@MyDataSourceResolver( @MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class, resolver = DefaultDataSourceResolver.class,

View File

@@ -0,0 +1,142 @@
package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.object.MyRelationParam;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.flow.dao.FlowDblinkMapper;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.service.FlowDblinkService;
import com.orangeforms.common.flow.util.FlowDataSourceUtil;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("flowDblinkService")
public class FlowDblinkServiceImpl extends BaseService<FlowDblink, Long> implements FlowDblinkService {
@Autowired
private FlowDblinkMapper flowDblinkMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private FlowDataSourceUtil dataSourceUtil;
/**
* 返回当前Service的主表Mapper对象。
*
* @return 主表Mapper对象。
*/
@Override
protected BaseDaoMapper<FlowDblink> mapper() {
return flowDblinkMapper;
}
@Transactional(rollbackFor = Exception.class)
@Override
public FlowDblink saveNew(FlowDblink flowDblink) {
flowDblinkMapper.insert(this.buildDefaultValue(flowDblink));
return flowDblink;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean update(FlowDblink flowDblink, FlowDblink originalFlowDblink) {
if (!StrUtil.equals(flowDblink.getConfiguration(), originalFlowDblink.getConfiguration())) {
dataSourceUtil.removeDataSource(flowDblink.getDblinkId());
}
flowDblink.setAppCode(TokenData.takeFromRequest().getAppCode());
flowDblink.setCreateUserId(originalFlowDblink.getCreateUserId());
flowDblink.setUpdateUserId(TokenData.takeFromRequest().getUserId());
flowDblink.setCreateTime(originalFlowDblink.getCreateTime());
flowDblink.setUpdateTime(new Date());
// 这里重点提示,在执行主表数据更新之前,如果有哪些字段不支持修改操作,请用原有数据对象字段替换当前数据字段。
UpdateWrapper<FlowDblink> uw = this.createUpdateQueryForNullValue(flowDblink, flowDblink.getDblinkId());
return flowDblinkMapper.update(flowDblink, uw) == 1;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean remove(Long dblinkId) {
dataSourceUtil.removeDataSource(dblinkId);
return flowDblinkMapper.deleteById(dblinkId) == 1;
}
@Override
public List<FlowDblink> getFlowDblinkList(FlowDblink filter, String orderBy) {
if (filter == null) {
filter = new FlowDblink();
}
filter.setAppCode(TokenData.takeFromRequest().getAppCode());
return flowDblinkMapper.getFlowDblinkList(filter, orderBy);
}
@Override
public List<FlowDblink> getFlowDblinkListWithRelation(FlowDblink filter, String orderBy) {
List<FlowDblink> resultList = this.getFlowDblinkList(filter, orderBy);
// 在缺省生成的代码中如果查询结果resultList不是Page对象说明没有分页那么就很可能是数据导出接口调用了当前方法。
// 为了避免一次性的大量数据关联,规避因此而造成的系统运行性能冲击,这里手动进行了分批次读取,开发者可按需修改该值。
int batchSize = resultList instanceof Page ? 0 : 1000;
this.buildRelationForDataList(resultList, MyRelationParam.normal(), batchSize);
return resultList;
}
@Override
public List<SqlTable> getDblinkTableList(FlowDblink dblink) {
List<SqlTable> resultList = dataSourceUtil.getTableList(dblink.getDblinkId(), null);
resultList.forEach(t -> t.setDblinkId(dblink.getDblinkId()));
return resultList;
}
@Override
public SqlTable getDblinkTable(FlowDblink dblink, String tableName) {
if (dblink.getDblinkId() == null || StrUtil.isBlank(tableName)) {
return null;
}
SqlTable sqlTable = dataSourceUtil.getTable(dblink.getDblinkId(), tableName);
sqlTable.setDblinkId(dblink.getDblinkId());
sqlTable.setColumnList(getDblinkTableColumnList(dblink, tableName));
return sqlTable;
}
@Override
public List<SqlTableColumn> getDblinkTableColumnList(FlowDblink dblink, String tableName) {
List<SqlTableColumn> columnList = dataSourceUtil.getTableColumnList(dblink.getDblinkId(), tableName);
columnList.forEach(c -> this.makeupSqlTableColumn(c, dblink.getDblinkType()));
return columnList;
}
private void makeupSqlTableColumn(SqlTableColumn sqlTableColumn, int dblinkType) {
sqlTableColumn.setDblinkType(dblinkType);
sqlTableColumn.setAutoIncrement("auto_increment".equals(sqlTableColumn.getExtra()));
}
private FlowDblink buildDefaultValue(FlowDblink flowDblink) {
flowDblink.setDblinkId(idGenerator.nextLongId());
TokenData tokenData = TokenData.takeFromRequest();
flowDblink.setCreateUserId(tokenData.getUserId());
flowDblink.setUpdateUserId(tokenData.getUserId());
Date now = new Date();
flowDblink.setCreateTime(now);
flowDblink.setUpdateTime(now);
flowDblink.setAppCode(tokenData.getAppCode());
return flowDblink;
}
}

View File

@@ -23,6 +23,7 @@ import com.orangeforms.common.flow.dao.FlowEntryPublishVariableMapper;
import com.orangeforms.common.flow.listener.*; import com.orangeforms.common.flow.listener.*;
import com.orangeforms.common.flow.model.*; import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.model.constant.FlowEntryStatus; import com.orangeforms.common.flow.model.constant.FlowEntryStatus;
import com.orangeforms.common.flow.model.constant.FlowEntryType;
import com.orangeforms.common.flow.model.constant.FlowVariableType; import com.orangeforms.common.flow.model.constant.FlowVariableType;
import com.orangeforms.common.flow.object.FlowElementExtProperty; import com.orangeforms.common.flow.object.FlowElementExtProperty;
import com.orangeforms.common.flow.object.FlowEntryExtensionData; import com.orangeforms.common.flow.object.FlowEntryExtensionData;
@@ -34,9 +35,7 @@ import com.orangeforms.common.flow.util.FlowCustomExtFactory;
import com.orangeforms.common.flow.util.FlowRedisKeyUtil; import com.orangeforms.common.flow.util.FlowRedisKeyUtil;
import com.orangeforms.common.redis.util.CommonRedisUtil; import com.orangeforms.common.redis.util.CommonRedisUtil;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper; import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService; import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Deployment;
@@ -45,12 +44,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -116,19 +109,16 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void publish(FlowEntry flowEntry, String initTaskInfo) throws XMLStreamException { public void publish(FlowEntry flowEntry, String initTaskInfo) {
commonRedisUtil.evictFormCache( commonRedisUtil.evictFormCache(FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey()));
FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey()));
FlowCategory flowCategory = flowCategoryService.getById(flowEntry.getCategoryId()); FlowCategory flowCategory = flowCategoryService.getById(flowEntry.getCategoryId());
InputStream xmlStream = new ByteArrayInputStream( BpmnModel bpmnModel = flowApiService.convertToBpmnModel(flowEntry.getBpmnXml());
flowEntry.getBpmnXml().getBytes(StandardCharsets.UTF_8));
@Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlStream);
BpmnXMLConverter converter = new BpmnXMLConverter();
BpmnModel bpmnModel = converter.convertToBpmnModel(reader);
bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName()); bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName());
bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey()); bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey());
this.processAutomaticTask(flowEntry, bpmnModel);
flowApiService.addProcessInstanceEndListener(bpmnModel, FlowFinishedListener.class); flowApiService.addProcessInstanceEndListener(bpmnModel, FlowFinishedListener.class);
List<FlowTaskExt> flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel); List<FlowTaskExt> flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel);
flowTaskExtList.forEach(t -> flowTaskExtService.verifyAutoTaskConfig(t));
if (StrUtil.isNotBlank(flowEntry.getExtensionData())) { if (StrUtil.isNotBlank(flowEntry.getExtensionData())) {
FlowEntryExtensionData flowEntryExtensionData = FlowEntryExtensionData flowEntryExtensionData =
JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class); JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class);
@@ -343,6 +333,18 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
return CallResult.ok(); return CallResult.ok();
} }
private void processAutomaticTask(FlowEntry flowEntry, BpmnModel bpmnModel) {
if (flowEntry.getFlowType().equals(FlowEntryType.NORMAL_TYPE)) {
return;
}
List<ReceiveTask> receiveTasks = bpmnModel.getMainProcess().getFlowElements()
.stream().filter(ReceiveTask.class::isInstance).map(ReceiveTask.class::cast).toList();
receiveTasks.forEach(r -> {
flowApiService.addExecutionListener(r, ReceiveTaskStartListener.class, "start", null);
flowApiService.addExecutionListener(r, ReceiveTaskEndListener.class, "end", null);
});
}
private void insertBuiltinEntryVariables(Long entryId) { private void insertBuiltinEntryVariables(Long entryId) {
Date now = new Date(); Date now = new Date();
FlowEntryVariable operationTypeVariable = new FlowEntryVariable(); FlowEntryVariable operationTypeVariable = new FlowEntryVariable();
@@ -447,6 +449,9 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
.filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c)); .filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c));
BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper();
for (FlowTaskExt t : flowTaskExtList) { for (FlowTaskExt t : flowTaskExtList) {
if (!elementMap.containsKey(t.getTaskId())) {
continue;
}
UserTask userTask = (UserTask) elementMap.get(t.getTaskId()); UserTask userTask = (UserTask) elementMap.get(t.getTaskId());
flowApiService.addTaskCreateListener(userTask, FlowUserTaskListener.class); flowApiService.addTaskCreateListener(userTask, FlowUserTaskListener.class);
Map<String, List<ExtensionAttribute>> attributes = userTask.getAttributes(); Map<String, List<ExtensionAttribute>> attributes = userTask.getAttributes();

View File

@@ -2,6 +2,7 @@ package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
@@ -15,7 +16,9 @@ import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.core.object.Tuple2; import com.orangeforms.common.core.object.Tuple2;
import com.orangeforms.common.core.util.DefaultDataSourceResolver; import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.flow.constant.FlowApprovalType; import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowAutoActionType;
import com.orangeforms.common.flow.constant.FlowConstant; import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.object.AutoTaskConfig;
import com.orangeforms.common.flow.object.FlowElementExtProperty; import com.orangeforms.common.flow.object.FlowElementExtProperty;
import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign; import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign;
import com.orangeforms.common.flow.object.FlowUserTaskExtData; import com.orangeforms.common.flow.object.FlowUserTaskExtData;
@@ -221,16 +224,29 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
return resultUserMapList; return resultUserMapList;
} }
private void buildUserMapList( @Override
List<FlowUserInfoVo> userInfoList, Set<String> loginNameSet, List<FlowUserInfoVo> userMapList) { public void verifyAutoTaskConfig(FlowTaskExt taskExt) {
if (CollUtil.isEmpty(userInfoList)) { if (StrUtil.isBlank(taskExt.getAutoConfigJson())) {
return; return;
} }
for (FlowUserInfoVo userInfo : userInfoList) { AutoTaskConfig taskConfig = JSON.parseObject(taskExt.getAutoConfigJson(), AutoTaskConfig.class);
if (!loginNameSet.contains(userInfo.getLoginName())) { this.verifyDataOperationTask(taskConfig);
loginNameSet.add(userInfo.getLoginName()); String errorMessage;
userMapList.add(userInfo); if (taskConfig.getActionType().equals(FlowAutoActionType.SELECT_ONE)) {
this.verifySrcTableNameEmpty(taskConfig);
} else if (taskConfig.getActionType().equals(FlowAutoActionType.AGGREGATION_CALC)) {
this.verifySrcTableNameEmpty(taskConfig);
if (CollUtil.isEmpty(taskConfig.getAggregationDataList())) {
errorMessage = StrFormatter.format("任务 [{}] 的聚合计算配置不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
} }
} else if (taskConfig.getActionType().equals(FlowAutoActionType.NUMBER_CALC)) {
if (StrUtil.isBlank(taskConfig.getCalculateFormula())) {
errorMessage = StrFormatter.format("任务 [{}] 的数值计算公式不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
} else if (taskConfig.getActionType().equals(FlowAutoActionType.HTTP)) {
this.verifyHttpConfig(taskConfig);
} }
} }
@@ -372,10 +388,93 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
return propertiesData; return propertiesData;
} }
private void verifyDataOperationTask(AutoTaskConfig taskConfig) {
String errorMessage;
if (taskConfig.getActionType().equals(FlowAutoActionType.ADD_NEW)) {
this.verifyDestTableNameEmpty(taskConfig);
if (CollUtil.isEmpty(taskConfig.getInsertDataList())) {
errorMessage = StrFormatter.format("任务 [{}] 的数据新增配置不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
} else if (taskConfig.getActionType().equals(FlowAutoActionType.UPDATE)) {
this.verifyDestTableNameEmpty(taskConfig);
if (CollUtil.isEmpty(taskConfig.getUpdateDataList())) {
errorMessage = StrFormatter.format("任务 [{}] 的数据更新配置不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
} else if (taskConfig.getActionType().equals(FlowAutoActionType.DELETE)) {
this.verifyDestTableNameEmpty(taskConfig);
}
}
private void verifyDestTableNameEmpty(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getDestTableName())) {
String errorMessage = StrFormatter.format("任务 [{}] 的目标表不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
}
private void verifySrcTableNameEmpty(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getSrcTableName())) {
String errorMessage = StrFormatter.format("任务 [{}] 的源表不能为空!", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
}
private void verifyHttpConfig(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getHttpRequestInfo().getUrl())) {
String errorMessage = StrFormatter.format("任务 [{}] 的URL不能为空", taskConfig.getTaskName());
throw new MyRuntimeException(errorMessage);
}
}
private void buildUserMapList(
List<FlowUserInfoVo> userInfoList, Set<String> loginNameSet, List<FlowUserInfoVo> userMapList) {
if (CollUtil.isEmpty(userInfoList)) {
return;
}
for (FlowUserInfoVo userInfo : userInfoList) {
if (!loginNameSet.contains(userInfo.getLoginName())) {
loginNameSet.add(userInfo.getLoginName());
userMapList.add(userInfo);
}
}
}
private FlowTaskExt buildTaskExtByAutoTask(Task task) {
FlowTaskExt flowTaskExt = new FlowTaskExt();
flowTaskExt.setTaskId(task.getId());
flowTaskExt.setGroupType(FlowConstant.GROUP_TYPE_AUTO_EXEC);
Map<String, List<ExtensionElement>> extensionMap = task.getExtensionElements();
List<ExtensionElement> autoConfigElements = extensionMap.get("taskInfo");
if (CollUtil.isEmpty(autoConfigElements)) {
return flowTaskExt;
}
ExtensionElement autoConfigElement = autoConfigElements.get(0);
Map<String, List<ExtensionAttribute>> attributesMap = autoConfigElement.getAttributes();
if (attributesMap != null) {
List<ExtensionAttribute> attributes = attributesMap.get("data");
if (CollUtil.isNotEmpty(attributes)) {
ExtensionAttribute attribute = attributes.get(0);
if (StrUtil.isNotBlank(attribute.getValue())) {
AutoTaskConfig taskConfig = JSONObject.parseObject(attribute.getValue(), AutoTaskConfig.class);
taskConfig.setTaskKey(task.getId());
taskConfig.setTaskName(task.getName());
flowTaskExt.setAutoConfigJson(JSON.toJSONString(taskConfig));
}
}
}
return flowTaskExt;
}
private void doBuildTaskExtList(FlowElement element, List<FlowTaskExt> flowTaskExtList) { private void doBuildTaskExtList(FlowElement element, List<FlowTaskExt> flowTaskExtList) {
if (element instanceof UserTask) { if (element instanceof UserTask) {
FlowTaskExt flowTaskExt = this.buildTaskExtByUserTask((UserTask) element); FlowTaskExt flowTaskExt = this.buildTaskExtByUserTask((UserTask) element);
flowTaskExtList.add(flowTaskExt); flowTaskExtList.add(flowTaskExt);
} else if (element instanceof ServiceTask || element instanceof ReceiveTask) {
FlowTaskExt flowTaskExt = this.buildTaskExtByAutoTask((Task) element);
flowTaskExtList.add(flowTaskExt);
} else if (element instanceof SubProcess) { } else if (element instanceof SubProcess) {
Collection<FlowElement> flowElements = ((SubProcess) element).getFlowElements(); Collection<FlowElement> flowElements = ((SubProcess) element).getFlowElements();
for (FlowElement element1 : flowElements) { for (FlowElement element1 : flowElements) {

View File

@@ -0,0 +1,68 @@
package com.orangeforms.common.flow.service.impl;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.util.DefaultDataSourceResolver;
import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.flow.dao.FlowTransProducerMapper;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Date;
@Slf4j
@MyDataSourceResolver(
resolver = DefaultDataSourceResolver.class,
intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE)
@Service("flowTransProducerService")
public class FlowTransProducerServiceImpl extends BaseService<FlowTransProducer, Long> implements FlowTransProducerService {
@Autowired
private FlowTransProducerMapper flowTransProducerMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Override
protected BaseDaoMapper<FlowTransProducer> mapper() {
return flowTransProducerMapper;
}
@Transactional(rollbackFor = Exception.class)
@Override
public FlowTransProducer saveNew(FlowTransProducer data) {
if (data.getTransId() == null) {
data.setTransId(idGenerator.nextLongId());
}
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData != null) {
data.setAppCode(tokenData.getAppCode());
data.setCreateLoginName(tokenData.getLoginName());
data.setCreateUsername(tokenData.getShowName());
}
data.setCreateTime(new Date());
data.setTraceId(MyCommonUtil.getTraceId());
flowTransProducerMapper.insert(data);
return data;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateById(FlowTransProducer data) {
return mapper().updateById(data) != 0;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean removeById(Serializable transId) {
return mapper().deleteById(transId) != 0;
}
}

View File

@@ -0,0 +1,657 @@
package com.orangeforms.common.flow.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.constant.FieldFilterType;
import com.orangeforms.common.core.constant.GlobalDeletedFlag;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.object.Tuple2;
import com.orangeforms.common.dbutil.object.DatasetFilter;
import com.orangeforms.common.dbutil.object.SqlTable;
import com.orangeforms.common.dbutil.object.SqlTableColumn;
import com.orangeforms.common.dbutil.util.DataSourceUtil;
import com.orangeforms.common.flow.constant.FlowAutoActionType;
import com.orangeforms.common.flow.model.FlowAutoVariableLog;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.model.FlowEntryVariable;
import com.orangeforms.common.flow.model.FlowTaskExt;
import com.orangeforms.common.flow.object.*;
import com.orangeforms.common.flow.service.FlowApiService;
import com.orangeforms.common.flow.service.FlowAutoVariableLogService;
import com.orangeforms.common.flow.service.FlowDblinkService;
import com.orangeforms.common.flow.service.FlowTaskExtService;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import static com.orangeforms.common.flow.object.AutoTaskConfig.*;
import static com.orangeforms.common.flow.object.AutoTaskConfig.SRC_FILTER_SQL;
@Slf4j
@Component
public class AutoFlowHelper {
@Autowired
private FlowApiService flowApiService;
@Autowired
private FlowDblinkService flowDblinkService;
@Autowired
private FlowTaskExtService flowTaskExtService;
@Autowired
private FlowAutoVariableLogService flowAutoVariableLogService;
@Autowired
private FlowDataSourceUtil flowDataSourceUtil;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private RuntimeService runtimeService;
@Autowired
private RestTemplate restTemplate;
private static final String REGEX_VAR = "\\$\\{(.+?)\\}";
private static final ThreadLocal<JSONObject> START_AUTO_INIT_VARIABLES = ThreadLocal.withInitial(() -> null);
private static final List<FlowEntryVariable> SYSTEM_VARIABLES = CollUtil.newLinkedList();
static {
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("currentTime", "当前时间"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialUserId", "发起用户ID"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialLoginName", "发起用户登录名"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialShowName", "发起用户显示名"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("initialDeptId", "发起用户部门ID"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("tokenData", "Token令牌数据"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("logicNormal", "逻辑删除正常值"));
SYSTEM_VARIABLES.add(FlowEntryVariable.createSystemVariable("logicDelete", "逻辑删除删除值"));
}
/**
* 设置初始化变量,仅在启动自动化流程的时候存入。临时存入变量参数到线程本地化存储,以后任务监听器的读取。
*
* @param variables 自动化流程启动时的初始变量对象。
*/
public static void setStartAutoInitVariables(JSONObject variables) {
START_AUTO_INIT_VARIABLES.set(variables);
}
/**
* 获取自动化流程启动时传入的初始变量数据。
*
* @return 自动化流程启动时传入的初始变量数据。
*/
public static JSONObject getStartAutoInitVariables() {
return START_AUTO_INIT_VARIABLES.get();
}
/**
* 清空该存储数据,主动释放线程本地化存储资源。
*/
public static void clearStartAutoInitVariables() {
START_AUTO_INIT_VARIABLES.remove();
}
public static List<FlowEntryVariable> systemFlowEntryVariables() {
return SYSTEM_VARIABLES;
}
/**
* 获取实时的系统变量。
*
* @return 系统变量键值对对象。
*/
public JSONObject getRealtimeSystemVariables() {
JSONObject systemVariables = new JSONObject();
systemVariables.put("currentTime", new Date());
return systemVariables;
}
/**
* 获取非实时的系统变量。
*
* @return 系统变量键值对对象。
*/
public JSONObject getNonRealtimeSystemVariables() {
JSONObject systemVariables = this.getRealtimeSystemVariables();
TokenData tokenData = TokenData.takeFromRequest();
systemVariables.put("initialUserId", tokenData != null ? tokenData.getUserId() : null);
systemVariables.put("initialLoginName", tokenData != null ? tokenData.getLoginName() : null);
systemVariables.put("initialShowName", tokenData != null ? tokenData.getShowName() : null);
systemVariables.put("initialDeptId", tokenData != null ? tokenData.getDeptId() : null);
systemVariables.put("tokenData", tokenData != null ? tokenData.getToken() : null);
systemVariables.put("logicNormal", GlobalDeletedFlag.NORMAL);
systemVariables.put("logicDelete", GlobalDeletedFlag.DELETED);
return systemVariables;
}
/**
* 解析自动化任务配置对象。
*
* @param processDefinitionId 流程定义Id。
* @param taskKey 流程任务定义标识。
* @return 自动化任务的配置对象。
*/
public AutoTaskConfig parseAutoTaskConfig(String processDefinitionId, String taskKey) {
FlowTaskExt taskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(processDefinitionId, taskKey);
return JSON.parseObject(taskExt.getAutoConfigJson(), AutoTaskConfig.class);
}
/**
* 指定指定的任务。
*
* @param transId 自动化任务执行时的生产者流水号Id。
* @param taskConfig 自动化任务配置对象。
* @param d 当前的执行委托对象。
*/
public void executeTask(Long transId, AutoTaskConfig taskConfig, DelegateExecution d) {
JSONObject variableData = this.getAutomaticVariable(d.getProcessInstanceId());
FlowDblink destDblink = new FlowDblink();
destDblink.setDblinkId(taskConfig.getDestDblinkId());
destDblink.setDblinkType(taskConfig.getDestDblinkType());
SqlTable destTable = flowDblinkService.getDblinkTable(destDblink, taskConfig.getDestTableName());
if (taskConfig.getActionType().equals(FlowAutoActionType.ADD_NEW)) {
List<AutoExecData> execDataList = this.makeInsertAutoExecData(taskConfig, destTable, variableData);
this.doExecuteSql(execDataList);
} else if (taskConfig.getActionType().equals(FlowAutoActionType.UPDATE)) {
AutoExecData execData = this.makeUpdateAutoExecData(taskConfig, destTable, variableData);
this.doExecuteSql(CollUtil.newArrayList(execData));
} else if (taskConfig.getActionType().equals(FlowAutoActionType.DELETE)) {
AutoExecData execData = this.makeDeleteAutoExecData(taskConfig, destTable, variableData);
this.doExecuteSql(CollUtil.newArrayList(execData));
} else if (taskConfig.getActionType().equals(FlowAutoActionType.SELECT_ONE)) {
this.doQueryOne(taskConfig, variableData, d);
} else if (taskConfig.getActionType().equals(FlowAutoActionType.HTTP)) {
this.doHttpRequest(transId, taskConfig, variableData, d);
}
}
private void doExecuteSql(List<AutoExecData> autoExecDataList) {
if (CollUtil.isEmpty(autoExecDataList)) {
return;
}
Connection connection = null;
try {
connection = flowDataSourceUtil.getConnection(autoExecDataList.get(0).getDblinkId());
connection.setAutoCommit(false);
for (AutoExecData autoExecData : autoExecDataList) {
flowDataSourceUtil.execute(connection, autoExecData.getSql(), autoExecData.getParams());
}
connection.commit();
} catch (Exception e) {
if (connection != null) {
try {
connection.rollback();
} catch (SQLException ex) {
log.error(e.getMessage(), e);
}
}
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
log.error(e.getMessage(), e);
}
}
}
}
private List<AutoExecData> makeInsertAutoExecData(
AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
List<AutoExecData> resultList = new LinkedList<>();
SqlTableColumn primaryKeyColumn = destTable.getColumnList().stream()
.filter(c -> BooleanUtil.isTrue(c.getPrimaryKey())).findFirst().orElse(null);
Map<String, SqlTableColumn> destColumnMap =
destTable.getColumnList().stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, c -> c));
ValueInfo pkValueInfo = null;
if (primaryKeyColumn != null) {
pkValueInfo = taskConfig.getInsertDataList().stream()
.filter(valueInfo -> valueInfo.getDestColumnName().equals(primaryKeyColumn.getColumnName()))
.findFirst().orElse(null);
}
//查询源数据。
List<Map<String, Object>> srcResultList = this.getSrcTableDataList(taskConfig, variableData);
if (CollUtil.isEmpty(srcResultList)) {
srcResultList.add(new HashMap<>(1));
}
for (Map<String, Object> srcResult : srcResultList) {
List<Object> params = new LinkedList<>();
StringBuilder sqlBuilder = new StringBuilder(1024);
sqlBuilder.append("INSERT INTO ").append(taskConfig.getDestTableName()).append("(");
if (primaryKeyColumn != null) {
Object param = this.calculatePrimaryKeyParam(primaryKeyColumn, pkValueInfo, srcResult);
if (param != null) {
sqlBuilder.append(primaryKeyColumn.getColumnName()).append(",");
params.add(param);
}
}
for (ValueInfo valueInfo : taskConfig.getInsertDataList()) {
if (pkValueInfo == null || !pkValueInfo.equals(valueInfo)) {
sqlBuilder.append(valueInfo.getDestColumnName()).append(",");
SqlTableColumn column = destColumnMap.get(valueInfo.getDestColumnName());
String destFieldType = flowDataSourceUtil.convertToJavaType(column, taskConfig.getDestDblinkType());
Object value = this.calculateValue(
valueInfo.getType(), valueInfo.getSrcValue(), destFieldType, srcResult, variableData);
params.add(value);
}
}
sqlBuilder.setCharAt(sqlBuilder.length() - 1, ')');
sqlBuilder.append(" VALUES(");
params.forEach(p -> sqlBuilder.append("?,"));
sqlBuilder.setCharAt(sqlBuilder.length() - 1, ')');
AutoExecData execData = new AutoExecData();
execData.setDblinkId(taskConfig.getDestDblinkId());
execData.setSql(sqlBuilder.toString());
execData.setParams(params);
resultList.add(execData);
}
return resultList;
}
private AutoExecData makeUpdateAutoExecData(AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
Map<String, SqlTableColumn> destColumnMap =
destTable.getColumnList().stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, c -> c));
List<Object> params = new LinkedList<>();
StringBuilder sqlBuilder = new StringBuilder(1024);
sqlBuilder.append("UPDATE ").append(taskConfig.getDestTableName()).append(" SET ");
for (ValueInfo valueInfo : taskConfig.getUpdateDataList()) {
sqlBuilder.append(valueInfo.getDestColumnName()).append(" = ?,");
SqlTableColumn column = destColumnMap.get(valueInfo.getDestColumnName());
String destFieldType = flowDataSourceUtil.convertToJavaType(column, taskConfig.getDestDblinkType());
Object value = this.calculateValue(
valueInfo.getType(), valueInfo.getSrcValue(), destFieldType, null, variableData);
params.add(value);
}
sqlBuilder.setCharAt(sqlBuilder.length() - 1, ' ');
Tuple2<String, List<Object>> result = this.calculateWhereClause(taskConfig, destTable, variableData);
sqlBuilder.append(result.getFirst());
CollUtil.addAll(params, result.getSecond());
AutoExecData execData = new AutoExecData();
execData.setDblinkId(taskConfig.getDestDblinkId());
execData.setSql(sqlBuilder.toString());
execData.setParams(params);
return execData;
}
private AutoExecData makeDeleteAutoExecData(AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
if (StrUtil.isNotBlank(taskConfig.getLogicDeleteField())) {
List<ValueInfo> updateDataList = new LinkedList<>();
ValueInfo logicDeleteValueInfo = new ValueInfo();
logicDeleteValueInfo.setDestColumnName(taskConfig.getLogicDeleteField());
logicDeleteValueInfo.setType(AutoTaskConfig.FIXED_VALUE);
logicDeleteValueInfo.setSrcValue(String.valueOf(GlobalDeletedFlag.DELETED));
updateDataList.add(logicDeleteValueInfo);
taskConfig.setUpdateDataList(updateDataList);
return this.makeUpdateAutoExecData(taskConfig, destTable, variableData);
}
StringBuilder sqlBuilder = new StringBuilder(1024);
sqlBuilder.append("DELETE FROM ").append(taskConfig.getDestTableName());
Tuple2<String, List<Object>> result = this.calculateWhereClause(taskConfig, destTable, variableData);
sqlBuilder.append(result.getFirst());
AutoExecData execData = new AutoExecData();
execData.setDblinkId(taskConfig.getDestDblinkId());
execData.setSql(sqlBuilder.toString());
execData.setParams(result.getSecond());
return execData;
}
private void doQueryOne(AutoTaskConfig taskConfig, JSONObject variableData, DelegateExecution d) {
List<Map<String, Object>> srcResultList = this.getSrcTableDataList(taskConfig, variableData);
Map<String, Object> srcResult = null;
if (CollUtil.isNotEmpty(srcResultList)) {
srcResult = srcResultList.get(0);
}
this.refreshAutoVariableLog(d, taskConfig.getTaskKey(), srcResult);
}
private void doHttpRequest(Long transId, AutoTaskConfig taskConfig, JSONObject variableData, DelegateExecution d) {
AutoHttpRequestInfo req = taskConfig.getHttpRequestInfo();
AutoHttpResponseData resp = taskConfig.getHttpResponnseData();
String body = this.buildRequestBody(req, variableData);
HttpHeaders headers = this.buildHttpHeaders(req, variableData);
headers.add(ApplicationConstant.HTTP_HEADER_TRANS_ID, transId.toString());
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(req.getUrl());
if (CollUtil.isNotEmpty(req.getUrlParamList())) {
for (AutoTaskConfig.ValueInfo valueInfo : req.getUrlParamList()) {
String paramValue = this.calculateValue(valueInfo.getType(), valueInfo.getSrcValue(), variableData);
uriBuilder.queryParam(valueInfo.getKey(), paramValue);
}
}
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(
uriBuilder.encode().toUriString(), HttpMethod.valueOf(req.getHttpMethod()), httpEntity, JSONObject.class);
try {
this.handleHttpResponseFail(req, resp, responseEntity);
this.refreshAutoVariableLog(d, taskConfig.getTaskKey(), responseEntity.getBody());
} catch (MyRuntimeException e) {
if (!resp.getFailHandleType().equals(AutoHttpResponseData.CONTINUE_ON_FAIL)) {
throw e;
}
log.error(e.getMessage(), e);
}
}
private Object calculatePrimaryKeyParam(
SqlTableColumn primaryKeyColumn, ValueInfo pkValueInfo, Map<String, Object> srcResult) {
// 说明目标表的主键值来自于源表的字段值。
if (pkValueInfo != null) {
return srcResult.get(pkValueInfo.getSrcValue());
}
if (BooleanUtil.isFalse(primaryKeyColumn.getAutoIncrement())) {
return primaryKeyColumn.getStringPrecision() == null ? idGenerator.nextLongId() : idGenerator.nextStringId();
}
return null;
}
private List<Map<String, Object>> getSrcTableDataList(AutoTaskConfig taskConfig, JSONObject variableData) {
if (ObjectUtil.isEmpty(taskConfig.getSrcDblinkId()) || StrUtil.isBlank(taskConfig.getSrcTableName())) {
return new LinkedList<>();
}
this.appendLogicDeleteFilter(taskConfig);
FlowDblink srcDblink = new FlowDblink();
srcDblink.setDblinkId(taskConfig.getSrcDblinkId());
srcDblink.setDblinkType(taskConfig.getSrcDblinkType());
SqlTable srcTable = flowDblinkService.getDblinkTable(srcDblink, taskConfig.getSrcTableName());
StringBuilder sqlBuilder = new StringBuilder(256);
String selectFields = CollUtil.isEmpty(taskConfig.getSelectFieldList())
? "*" : CollUtil.join(taskConfig.getSelectFieldList(), ",");
sqlBuilder.append("SELECT ").append(selectFields).append(" FROM ").append(srcTable.getTableName());
if (taskConfig.getSrcFilterType().equals(AutoTaskConfig.SRC_FILTER_SQL)) {
Tuple2<String, List<Object>> result = this.calcualteCustomSqlFilter(taskConfig.getSrcFilterSql(), variableData);
if (StrUtil.isNotBlank(result.getFirst())) {
sqlBuilder.append(result.getFirst());
}
try {
return flowDataSourceUtil.query(taskConfig.getSrcDblinkId(), sqlBuilder.toString(), result.getSecond());
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
}
}
DatasetFilter dataFilter = null;
if (CollUtil.isNotEmpty(taskConfig.getSrcFilterList())) {
dataFilter = this.calculateDatasetFilter(
srcTable, taskConfig.getSrcDblinkType(), taskConfig.getSrcFilterList(), variableData);
}
try {
Tuple2<String, List<Object>> result =
flowDataSourceUtil.buildWhereClauseByFilters(taskConfig.getSrcDblinkId(), dataFilter);
sqlBuilder.append(result.getFirst());
return flowDataSourceUtil.query(taskConfig.getSrcDblinkId(), sqlBuilder.toString(), result.getSecond());
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new MyRuntimeException(e);
}
}
private void refreshAutoVariableLog(DelegateExecution d, String outputVariableName, Map<String, Object> newResult) {
if (StrUtil.isBlank(outputVariableName)) {
return;
}
runtimeService.setVariable(d.getId(), outputVariableName, newResult);
FlowAutoVariableLog autoVariableLog =
flowAutoVariableLogService.getAutoVariableByProcessInstanceId(d.getProcessInstanceId());
JSONObject latestVariableData = JSON.parseObject(autoVariableLog.getVariableData());
latestVariableData.put(outputVariableName, newResult);
autoVariableLog.setVariableData(JSON.toJSONString(latestVariableData));
flowAutoVariableLogService.updateById(autoVariableLog);
}
private Tuple2<String, List<Object>> calculateWhereClause(AutoTaskConfig taskConfig, SqlTable destTable, JSONObject variableData) {
if (taskConfig.getDestFilterType().equals(AutoTaskConfig.SRC_FILTER_SQL)) {
return this.calcualteCustomSqlFilter(taskConfig.getDestFilterSql(), variableData);
}
if (CollUtil.isNotEmpty(taskConfig.getDestFilterList())) {
DatasetFilter dataFilter = this.calculateDatasetFilter(
destTable, taskConfig.getDestDblinkType(), taskConfig.getDestFilterList(), variableData);
return flowDataSourceUtil.buildWhereClauseByFilters(taskConfig.getDestDblinkId(), dataFilter);
}
return new Tuple2<>(StrUtil.EMPTY, new LinkedList<>());
}
private Tuple2<String, List<Object>> calcualteCustomSqlFilter(String filterSql, JSONObject variableData) {
Tuple2<String, List<String>> result = this.findAndReplaceAllVariables(filterSql);
String whereClause = result.getFirst();
if (StrUtil.isNotBlank(whereClause)) {
whereClause = DataSourceUtil.SQL_WHERE + whereClause;
}
List<Object> params = this.extractVariableParamsByVariable(result.getSecond(), variableData);
return new Tuple2<>(whereClause, params);
}
private DatasetFilter calculateDatasetFilter(
SqlTable table, Integer dblinkType, List<FilterInfo> filterInfoList, JSONObject variableData) {
Map<String, SqlTableColumn> columnMap = table.getColumnList()
.stream().collect(Collectors.toMap(SqlTableColumn::getColumnName, c -> c));
DatasetFilter dataFilter = new DatasetFilter();
filterInfoList.forEach(filterInfo -> {
DatasetFilter.FilterInfo filter = new DatasetFilter.FilterInfo();
filter.setFilterType(filterInfo.getFilterType());
filter.setParamName(filterInfo.getFilterColumnName());
SqlTableColumn column = columnMap.get(filterInfo.getFilterColumnName());
String fieldType = flowDataSourceUtil.convertToJavaType(column, dblinkType);
if (filterInfo.getFilterType().equals(FieldFilterType.IN)) {
filter.setParamValueList(flowDataSourceUtil.convertToColumnValues(fieldType, filterInfo.getFilterValue()));
} else {
Object convertedValue = calculateValue(
filterInfo.getValueType(), filterInfo.getFilterValue(), fieldType, null, variableData);
filter.setParamValue(convertedValue);
}
dataFilter.add(filter);
});
return dataFilter;
}
private void appendLogicDeleteFilter(AutoTaskConfig taskConfig) {
if (StrUtil.isBlank(taskConfig.getLogicDeleteField())) {
return;
}
if (taskConfig.getSrcFilterType().equals(SRC_FILTER_SQL)) {
StringBuilder sb = new StringBuilder(512);
if (StrUtil.isNotBlank(taskConfig.getSrcFilterSql())) {
sb.append("(").append(taskConfig.getSrcFilterSql())
.append(") AND ")
.append(taskConfig.getLogicDeleteField())
.append("=")
.append(GlobalDeletedFlag.NORMAL);
} else {
sb.append(taskConfig.getLogicDeleteField()).append("=").append(GlobalDeletedFlag.NORMAL);
}
taskConfig.setSrcFilterSql(sb.toString());
} else {
List<FilterInfo> filterInfoList = taskConfig.getSrcFilterList();
if (filterInfoList == null) {
filterInfoList = new LinkedList<>();
}
FilterInfo logicDeleteFilter = new FilterInfo();
logicDeleteFilter.setFilterColumnName(taskConfig.getLogicDeleteField());
logicDeleteFilter.setFilterType(FieldFilterType.EQUAL);
logicDeleteFilter.setValueType(AutoTaskConfig.FIXED_VALUE);
logicDeleteFilter.setFilterValue(String.valueOf(GlobalDeletedFlag.NORMAL));
filterInfoList.add(logicDeleteFilter);
taskConfig.setSrcFilterList(filterInfoList);
}
}
private String buildRequestBody(AutoHttpRequestInfo req, JSONObject variableData) {
String body = null;
if (StrUtil.equals(req.getBodyType(), AutoHttpRequestInfo.BODY_TYPE_RAW)) {
body = this.replaceAllVariables(req.getBodyData(), variableData);
} else {
StringBuilder sb = new StringBuilder(256);
if (CollUtil.isNotEmpty(req.getFormDataList())) {
for (ValueInfo valueInfo : req.getFormDataList()) {
String value = this.calculateValue(valueInfo.getType(), valueInfo.getSrcValue(), variableData);
sb.append(valueInfo.getKey()).append("=").append(value).append("&");
}
body = sb.substring(0, sb.length() - 1);
}
}
return body;
}
private HttpHeaders buildHttpHeaders(AutoHttpRequestInfo req, JSONObject variableData) {
HttpHeaders headers = new HttpHeaders();
if (CollUtil.isNotEmpty(req.getHeaderList())) {
for (AutoTaskConfig.ValueInfo valueInfo : req.getHeaderList()) {
String value = this.calculateValue(valueInfo.getType(), valueInfo.getSrcValue(), variableData);
headers.add(valueInfo.getKey(), value);
}
}
if (StrUtil.equals(req.getBodyType(), AutoHttpRequestInfo.BODY_TYPE_RAW)) {
headers.setContentType(MediaType.APPLICATION_JSON);
} else {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
}
return headers;
}
private void handleHttpResponseFail(
AutoHttpRequestInfo req, AutoHttpResponseData resp, ResponseEntity<JSONObject> responseEntity) {
Set<Integer> successStatusCodes = CollUtil.newHashSet(HttpStatus.HTTP_OK);
if (StrUtil.isNotBlank(resp.getSuccessStatusCode())) {
successStatusCodes = StrUtil.split(resp.getSuccessStatusCode(), StrUtil.COMMA)
.stream().map(Integer::valueOf).collect(Collectors.toSet());
}
// 先判断HttpStatus是否正确。
if (!CollUtil.contains(successStatusCodes, responseEntity.getStatusCode().value())) {
String cancelReason = StrFormatter.format(
"Failed to Rquest Url [{}] with StatusCode [{}]", req.getUrl(), responseEntity.getStatusCode().value());
throw new MyRuntimeException(cancelReason);
}
// 如果没有配置应答体中标记是否成功的字段,则视为成功。
if (StrUtil.isBlank(resp.getSuccessBodyField())) {
return;
}
JSONObject responseData = responseEntity.getBody();
String successFlagField = JSONPath.compile(resp.getSuccessBodyField()).eval(responseData, String.class);
// 如果应答体中标记是否成功的字段你不存在或者是应答体中标记为成功的字段值为true则视为成功。
if (StrUtil.isBlank(successFlagField)
|| StrUtil.equals(successFlagField, "[]")
|| BooleanUtil.toBooleanObject(successFlagField).equals(Boolean.TRUE)) {
return;
}
// 开始处理失败场景。
String cancelReason;
if (StrUtil.isNotBlank(resp.getErrorMessageBodyField())) {
String errorMsgPath = "$." + resp.getErrorMessageBodyField();
String errorMsg = JSONPath.compile(errorMsgPath).eval(responseData, String.class);
cancelReason = StrFormatter.format(
"Failed to Rquest Url [{}] with errorMsg [{}]", req.getUrl(), errorMsg);
} else {
cancelReason = StrFormatter.format("Failed to Rquest Url [{}]", req.getUrl());
}
throw new MyRuntimeException(cancelReason);
}
private Tuple2<String, List<String>> findAndReplaceAllVariables(String s) {
List<String> variables = ReUtil.findAll(REGEX_VAR, s, 0);
if (CollUtil.isNotEmpty(variables)) {
s = s.replaceAll(REGEX_VAR, "?");
variables = variables.stream().map(v -> v.substring(2, v.length() - 1)).collect(Collectors.toList());
}
return new Tuple2<>(s, variables);
}
private String replaceAllVariables(String s, JSONObject variableData) {
if (StrUtil.isNotBlank(s)) {
List<String> variables = ReUtil.findAll(REGEX_VAR, s, 0);
if (CollUtil.isNotEmpty(variables)) {
for (String v : variables) {
s = StrUtil.replace(s, v, variableData.getString(v.substring(2, v.length() - 1)));
}
}
}
return s;
}
private JSONObject getAutomaticVariable(String processInstanceId) {
JSONObject variableData = getStartAutoInitVariables();
if (variableData == null) {
FlowAutoVariableLog v = flowAutoVariableLogService.getAutoVariableByProcessInstanceId(processInstanceId);
if (v != null) {
variableData = JSON.parseObject(v.getVariableData());
}
}
JSONObject systemVariables = this.getRealtimeSystemVariables();
if (variableData == null) {
return systemVariables;
}
variableData.putAll(systemVariables);
return variableData;
}
private List<Object> extractVariableParamsByVariable(List<String> variableNames, JSONObject variableData) {
List<Object> resultList = new LinkedList<>();
if (CollUtil.isEmpty(variableNames) || MapUtil.isEmpty(variableData)) {
return resultList;
}
for (String name : variableNames) {
Object value = this.verifyAndGetVariableExist(name, variableData);
if (value != null) {
resultList.add(value);
}
}
return resultList;
}
private Object calculateValue(
Integer valueType, String value, String fieldType, Map<String, Object> srcResult, JSONObject variableData) {
if (valueType.equals(AutoTaskConfig.FIXED_VALUE)) {
return flowDataSourceUtil.convertToColumnValue(fieldType, value);
} else if (valueType.equals(AutoTaskConfig.COLUMN_VALUE)) {
return srcResult.get(value);
}
Object variableValue = this.verifyAndGetVariableExist(value, variableData);
return flowDataSourceUtil.convertToColumnValue(fieldType, (Serializable) variableValue);
}
private String calculateValue(Integer valueType, String value, JSONObject variableData) {
if (valueType.equals(AutoTaskConfig.FIXED_VALUE)) {
return value;
}
Object variableValue = this.verifyAndGetVariableExist(value, variableData);
return variableValue == null ? null : variableValue.toString();
}
private Object verifyAndGetVariableExist(String name, JSONObject variableData) {
String variableName = name;
if (StrUtil.contains(name, StrUtil.DOT)) {
variableName = StrUtil.subBefore(name, StrUtil.DOT, false);
}
if (!variableData.containsKey(variableName)) {
throw new MyRuntimeException(StrFormatter.format("变量值 [{}] 不存在!", name));
}
if (!StrUtil.contains(name, StrUtil.DOT)) {
return variableData.get(variableName);
}
JSONObject variableObject = variableData.getJSONObject(variableName);
if (variableObject == null) {
return null;
}
String variablePath = StrUtil.subAfter(name, StrUtil.DOT, false);
return JSONPath.compile(variablePath).eval(variableObject);
}
}

View File

@@ -0,0 +1,47 @@
package com.orangeforms.common.flow.util;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.dbutil.provider.DataSourceProvider;
import com.orangeforms.common.dbutil.util.DataSourceUtil;
import com.orangeforms.common.flow.model.FlowDblink;
import com.orangeforms.common.flow.service.FlowDblinkService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 工作流模块动态加载的数据源工具类。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@Component
public class FlowDataSourceUtil extends DataSourceUtil {
@Autowired
private FlowDblinkService dblinkService;
@Override
protected int getDblinkTypeByDblinkId(Long dblinkId) {
DataSourceProvider provider = this.dblinkProviderMap.get(dblinkId);
if (provider != null) {
return provider.getDblinkType();
}
FlowDblink dblink = dblinkService.getById(dblinkId);
if (dblink == null) {
throw new MyRuntimeException("Flow DblinkId [" + dblinkId + "] doesn't exist!");
}
this.dblinkProviderMap.put(dblinkId, this.getProvider(dblink.getDblinkType()));
return dblink.getDblinkType();
}
@Override
protected String getDblinkConfigurationByDblinkId(Long dblinkId) {
FlowDblink dblink = dblinkService.getById(dblinkId);
if (dblink == null) {
throw new MyRuntimeException("Flow DblinkId [" + dblinkId + "] doesn't exist!");
}
return dblink.getConfiguration();
}
}

View File

@@ -0,0 +1,59 @@
package com.orangeforms.common.flow.util;
import com.alibaba.fastjson.JSON;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.model.FlowTransProducer;
import com.orangeforms.common.flow.service.FlowTransProducerService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
/**
* 流程监听器发送spring事件的帮助类。
* 注意流程的监听器不是bean对象不能发送和捕捉事件因此需要借助该类完成。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@Component
public class ListenerEventPublishHelper {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private RuntimeService runtimeService;
@Autowired
private FlowTransProducerService flowTransProducerService;
public <T> void publishEvent(T data) {
eventPublisher.publishEvent(data);
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void doHandle(FlowTransProducer producerData) {
Map<String, Object> transVariableMap = new HashMap<>(1);
transVariableMap.put(FlowConstant.AUTO_FLOW_TRANS_PRODUCER_VAR, producerData);
Executors.newSingleThreadExecutor().submit(() -> this.triggerReceiveTask(producerData, transVariableMap));
}
private void triggerReceiveTask(FlowTransProducer producerData, Map<String, Object> transVariableMap) {
try {
runtimeService.trigger(producerData.getExecutionId(), null, transVariableMap);
} catch (Exception e) {
log.error("Failed to commit automatic business data [** " + JSON.toJSONString(producerData) + " **]", e);
producerData.setErrorReason(e.getMessage());
flowTransProducerService.updateById(producerData);
throw new MyRuntimeException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,84 @@
package com.orangeforms.common.flow.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
import java.util.Map;
/**
* 工作流数据表所在数据库链接VO对象。
*
* @author Jerry
* @date 2024-07-02
*/
@Schema(description = "工作流数据表所在数据库链接VO对象")
@Data
public class FlowDblinkVo {
/**
* 主键Id。
*/
@Schema(description = "主键Id")
private Long dblinkId;
/**
* 应用编码。为空时,表示非第三方应用接入。
*/
@Schema(description = "应用编码。为空时,表示非第三方应用接入")
private String appCode;
/**
* 链接中文名称。
*/
@Schema(description = "链接中文名称")
private String dblinkName;
/**
* 链接描述。
*/
@Schema(description = "链接描述")
private String dblinkDescription;
/**
* 配置信息。
*/
@Schema(description = "配置信息")
private String configuration;
/**
* 数据库链接类型。
*/
@Schema(description = "数据库链接类型")
private Integer dblinkType;
/**
* 更新者。
*/
@Schema(description = "更新者")
private Long updateUserId;
/**
* 更新时间。
*/
@Schema(description = "更新时间")
private Date updateTime;
/**
* 创建者。
*/
@Schema(description = "创建者")
private Long createUserId;
/**
* 创建时间。
*/
@Schema(description = "创建时间")
private Date createTime;
/**
* 数据库链接类型常量字典关联数据。
*/
@Schema(description = "数据库链接类型常量字典关联数据")
private Map<String, Object> dblinkTypeDictMap;
}

Binary file not shown.

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