preface

As a developer, you’ll be exposed to workflow engines, such as Activiti, JBPM, etc., and these workflow engines tend to be heavy. It’s not very elegant to introduce it in a small project. This article mainly introduces the principle of workflow engine and how to build a suitable workflow engine.

Process node

When the project needs a number of different procedures (processes) or divided into a number of stages to complete, a program or a stage end, another program or a stage at the beginning of the transfer point (category point or time point), called process node. Common process nodes are: start end, task node, condition node, branch node, merge node, sub-process node, end node, etc. For the sake of simplicity, only the simplest process (start -> Task -> End) is covered here.

Start node

At the beginning of the process, do some initialization of the node.

  • The type of START

  • Model class StartModel

  • Attributes that

    The property name type instructions
    id String Node ID, the unique identification of the process node
    name String Node name, what does the node do
    nextNodeId String Id of the next node
    ext Map Extended attributes

Task node

This node generates tasks. You can proceed to the next step only after the tasks are complete

  • The type of TASK
  • Model class TaskModel
  • Attributes that
The property name type instructions
id String Node ID, the unique identification of the process node
name String Node name, what does the node do
nextNodeId String Id of the next node
performType String Participation mode (ANY-> Next step can be performed when ALL-> next step is completed)
expireTime String Expected completion time (expected completion of the generated task)
reminderTime String Time to remind (e.g. when to remind if the task is not processed)
reminderRepeat Integer Alert interval (minutes) (what is the alert rule if the task is not processed)
autoExecute Boolean Automatic execution (if the task is unprocessed and due, automatic execution)
ext Map Extended attributes

The end node

This object indicates that the process has completed, and it is here that the process completion event is triggered

  • Type END
  • Model class EndModel
  • Attributes that
The property name type instructions
id String Node ID, the unique identification of the process node
name String Node name, what does the node do
ext Map Extended attributes

Sample process definition

  • Press center release process
{
	"flowId":"cmsNews"."flowName":"Press Center press release"."nodeList": [{"id":"start"."nodeType": "START"."name":"Start"."nextNodeId": "collect"
		},
		{
			"id":"collect"."nodeType": "TASK"."name":"Collecting and editing news articles"."performType":"ANY"."nextNodeId": "firstReview"."ext": {"userIds":"1, 2, 3"}}, {"id":"firstReview"."nodeType": "TASK"."name":"Preliminary examination of propaganda Department of Party and Mass Affairs Department"."performType":"ANY"."nextNodeId": "secondReview"."ext": {"userIds":"1, 2, 3"}}, {"id":"secondReview"."nodeType": "TASK"."name":"Party and Mass Affairs Department second Instance"."performType":"ANY"."nextNodeId": "end"."ext": {"userIds":"1, 2, 3"}}, {"id":"end"."nodeType": "END"."name":"The end"}}]Copy the code

Table structure design

  • Flow_define (Process definition)
field type empty The default annotation
id char(36) False A primary key
flow_define_code varchar(16) True Process definition code
flow_name varchar(100) True The name of the process
description varchar(255) True The process description
flow_define mediumtext True Process Definition (JSON string)
create_time datetime True Creation time
update_time datetime True Update time
is_deleted tinyint(1) unsigned True 0 Delete (1-> delete YES,0-> not delete NO)
  • Flow_work (Process instance)
field type empty The default annotation
id char(36) False A primary key
flow_name varchar(100) True The name of the process
flow_define_code varchar(32) True Process definition code
last_operator char(36) True The last operator ID
current_node_id char(36) True Current Node ID
next_node_id char(36) True Next Node ID
flow_define mediumtext True Process Definition (JSON string)
status int(6) True Process status (10-> START START,30-> END,40-> CANCEL)
flow_param mediumtext True Process parameter JSON
create_time datetime True Creation time
update_time datetime True Update time
is_deleted tinyint(1) unsigned True 0 Delete (1-> delete YES,0-> not delete NO)
  • Flow_task (Process task)
field type empty The default annotation
id char(36) False A primary key
flow_work_id char(36) True Process id
flow_node_id char(36) True Process node ID
task_name varchar(100) True The name of the task
operator char(36) True Operator user ID
actor_user_id char(36) True Id of the executing user
status varchar(6) True 10 Task status (10-> CREATED,20-> FINISHED,30-> EXPIRED,40-> CANCEL,50-> REJECT)
service_type varchar(32) True Business types
service_id varchar(36) True Associated service ID
finish_time datetime True Completion time
flow_param mediumtext True Process parameter JSON
create_time datetime True Creation time
update_time datetime True Update time
is_deleted tinyint(1) unsigned True 0 Delete (1-> delete YES,0-> not delete NO)

Description of process keywords

  • About the process definition file

A flow definition file is a file that describes the relationship between nodes in a flow. It is described in JSON files and stored in the Flow_DEFINE field of the Flow_DEFINE table.

  • About process definition file parsing

Process definition file parsing is about parsing out the process nodes in the process definition file and assembling them into a process model.

  • About the process instance

A process instance is a process instance generated using a process definition and stored in the FLOW_WORK table. (This action is initiated by the business system, that is, initiating a process.)

  • About the task

Tasks are generated on the task node, that is, when the task node is executed, the processor of the node is used to assign tasks to the participants, and the assigned tasks are stored in the Flow_task table. You can query this table in the service system to obtain the to-do tasks of the login user.

  • About performing tasks

When the participants process the to-do task, the business system will call the task completion method, and the process engine will modify the task completion state and drive the process to the next node.

  • About rejecting the mission

When the participants reject the task, the business system will call the task rejection method, and the process engine will return the process progress to the previous node.

Core processing flow description

FlowWorkCtl.java

  • Start process instance
  1. Load flow definition
  2. Parse the process definition file
  3. Node processor controller calls perform node processing
    1. If it is a start node, only the start event needs to be published
    2. If it is a task node, assign tasks, issue start events, and update process instance node status based on node configuration (not required, just a simple record)
    3. If it is the end node, modify the process instance to complete and publish the end event.
  • Perform a task
  1. Getting a Task instance
  2. Getting a process instance
  3. Parse the process definition file
  4. Set the task to completed
  5. Check whether node tasks are complete
    1. If it is done, it takes the next node and calls the node processing controller to execute
    2. If no, no action is required
  • To reject the task
  1. Getting a Task instance
  2. Getting a process instance
  3. Parse the process definition file
  4. Set the task to Rejected
  5. Modify the tasks of other participants of the node to cancelled
  6. Take the last node model and call the node processing controller to execute

Start coding

Project directory

Com. Mole. Modules. Flow ├ ─ ─ the entity# entity class, corresponding to table structure├ ─ ─ FlowDefine. Java# Process defines entities├ ─ ─ FlowTask. Java# Process task entity└ ─ ─ FlowWork. Java# Process instance entities├ ─ ─ enumsError code enumeration└ ─ ─ FlowErrEnum. Java ├ ─ ─ the event# events├ ─ ─ FlowEndNodeEvent. Java# Process end event├ ─ ─ FlowStartNodeEvent. Java# process start event├ ─ ─ FlowTaskCreatedEvent. Java# Process task creation event└ ─ ─ FlowTaskNode. Event. Java# Task starts execution event├ ─ ─ mapper# the persistence layer├── Heavy Exercises. ├─ Heavy Exercises. ├─ heavy Exercises# parameter entity└ ─ ─ FlowParam. Java# Process parameter entities├ ─ ─ model# model layer├ ─ ─ BaseModel. Java# Base node model├ ─ ─ EndModel. Java# End node model├ ─ ─ the FlowModel. Java# Process model├ ─ ─ StartModel. Java# start node model└ ─ ─ TaskModel. Java# Task node model├ ─ ─ parser └ ─ ─ FlowNodeParser. Java# Node resolver├ ─ ─ processor ├ ─ ─ impl ├ ─ ─ EndNodeProcessorImpl. JavaEnd node processing implementation class├ ─ ─ StartNodeProcessorImpl. JavaStart the node processing the implementation class└ ─ ─ TaskNodeProcessorImpl. JavaTask nodes handle implementation classes├ ─ ─ FlowNodeProcessor. JavaNode processing interface└ ─ ─ FlowNodeProcessorCtl. Java# node processor controller├ ─ ─ service └ ─ ─ FlowNodeUserService. Java# Custom node participant interface└ ─ ─ FlowWorkCtl. Java# Process control class - engine
Copy the code

Core classes

Model class

  • FlowModel.java
package com.mole.modules.flow.model;

import java.io.Serializable;
import java.util.List;
/** * process model *@author MLD
 *
 */
public class FlowModel implements Serializable{

	/ * * * * /
	private static final long serialVersionUID = -3978388377128544781L;
	/** * process id */
	private String flowId;
	/** * Process name */
	private String flowName;
	/** * process node */
	private List<BaseModel> nodeList;
	/ /... get ..... The set is a little
	/** * Obtain node * by id@param nodeId
	 * @return* /
	public BaseModel getNodeModel(String nodeId) {
		if(null == nodeList || nodeList.isEmpty()) {
			return null;
		}
		for(BaseModel node : nodeList) {
			if(node.getId().equals(nodeId)){
				returnnode; }}return null;
	}
	/** * get the start node model *@return* /
	public StartModel getStartModel(a) {
		if(null == nodeList || nodeList.isEmpty()) {
			return null;
		}
		for(BaseModel node : nodeList) {
			if(node instanceof StartModel){
				return(StartModel)node; }}return null;
	}
    /** * passes the previous node * of the current node@param nodeId
	 * @return* /
	public BaseModel getPreNodeModel(String nodeId) {
		if(null == nodeList || nodeList.isEmpty()) {
			return null;
		}
		for(BaseModel node : nodeList) {
			if(nodeId.equals(node.getNextNodeId())){
				returnnode; }}return null;
	}
	/** * Get end node model *@return* /
	public EndModel getEndModel(a) {
		if(null == nodeList || nodeList.isEmpty()) {
			return null;
		}
		for(BaseModel node : nodeList) {
			if(node instanceof EndModel){
				return(EndModel)node; }}return null; }}Copy the code
  • BaseModel.java
package com.mole.modules.flow.model;
import java.io.Serializable;
import java.util.Map
public class BaseModel implements Serializable {
    /** * Node ID */
    private String id;
    /** * Node name */
    private String name;
    /** * Node type */
	private String nodeType;
    /** * Next node ID */
    private String nextNodeId;
    /** * Extended attributes */
    private Map<String,Object> ext;
}
Copy the code
  • StartModel.java
package com.mole.modules.flow.model;
import java.io.Serializable;
public class StartModel extends BaseModel implements Serializable {}Copy the code
  • TaskModel.java
package com.mole.modules.flow.model;
import java.io.Serializable;
public class TaskModel extends BaseModel implements Serializable {
    /** * Participation mode (* ANY-> ANY participant can perform the next step, * ALL-> ALL participants can perform the next step *) */
    private String performType;
    /** * Expected completion time (when the generated task is expected to complete) */
    private String expireTime;
    /** * Alert time (such as the task is not processed, when to alert) */
    private String reminderTime;
    /** * Alert interval (minutes) (if the task is not processed, what is the alert rule) */
    private Integer reminderRepeat;
    /** * Whether to automatically execute (if the task is not processed and expires, whether to automatically execute) */
    private Boolean autoExecute;
}
Copy the code
  • EndModel
package com.mole.modules.flow.model;
import java.io.Serializable;
public class EndModel extends BaseModel implements Serializable {}Copy the code

Process node resolution classes

Convert the process definition JSON into the corresponding process model

  • FlowNodeParser.java
package com.mole.modules.flow.parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mole.modules.framework.util.JsonUtil;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.EndModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.model.StartModel;
import com.mole.modules.flow.model.TaskModel;
/** * node resolver *@author MLD
 *
 */
public class FlowNodeParser {
	private ObjectMapper objectMapper = new ObjectMapper();
	private final static String FLOW_ID="flowId";
	private final static String FLOW_NAME="flowName";
	private final static String NODE_LIST="nodeList";
	private final static String NODE_TYPE="nodeType";
	/** * Parse the process definition *@param flowDefine
	 * @return
	 * @throws IOException
	 */
	public FlowModel parser(String flowDefine) throws IOException {
		JsonNode root = objectMapper.readTree(flowDefine);
		if(! root.has(FLOW_ID)||! root.has(FLOW_NAME)||! root.has(NODE_LIST)) { } String flowId = root.get(FLOW_ID).asText(); String flowName = root.get(FLOW_NAME).asText(); JsonNode nodeList = root.get("nodeList");
		if(! nodeList.isArray()) {return null;
		}
		FlowModel flowModel = new FlowModel();
		flowModel.setFlowId(flowId);
		flowModel.setFlowName(flowName);
		flowModel.setNodeList(new ArrayList<BaseModel>());
		Iterator<JsonNode> nodes = nodeList.elements();
		while (nodes.hasNext()) {
			JsonNode node = nodes.next();
			if(node.has(NODE_TYPE)) {
				String nodeType = node.get(NODE_TYPE).asText();
				if("START".equals(nodeType)) {
					// Start node
					StartModel model = objectMapper.readValue(node.toString(), StartModel.class);
					flowModel.getNodeList().add(model);
				} else if("TASK".equals(nodeType)) {
					// Task node
					TaskModel model = objectMapper.readValue(node.toString(), TaskModel.class);
					flowModel.getNodeList().add(model);
				}	else if("END".equals(nodeType)) {
					// End the nodeEndModel model = objectMapper.readValue(node.toString(), EndModel.class); flowModel.getNodeList().add(model); }}}returnflowModel; }}Copy the code

Node processor class

Node processor mainly conducts different scheduling processing for different nodes. For example, the start node will drive the process to the next step, the task node will produce tasks, and the end node will end the process.

  • FlowNodeProcessor.java
package com.mole.modules.flow.processor;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.param.FlowParam;

/** * Node processor interface *@author MLD
 *
 */
public interface FlowNodeProcessor {
	/** * Type of node to process *@return* /
	public String getNodeType(a);
	/** * Process node processing method *@paramExample flowWork process *@paramFlowModel Current process model *@paramCurrentNodeModel currentNodeModel *@paramFlowParam Process parameters */
	public void process(FlowWork flowWork,FlowModel flowModel,BaseModel currentNddeModel,FlowParam flowParam);
}

Copy the code
  • StartNodeProcessorImpl.java
package com.mole.modules.flow.processor.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.event.FlowStartNodeEvent;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.model.StartModel;
import com.mole.modules.flow.param.FlowParam;
import com.mole.modules.flow.processor.FlowNodeProcessor;
/** * Start node processor *@author MLD
 *
 */
@Component
public class StartNodeProcessorImpl implements FlowNodeProcessor {
	@Autowired(required=false)
	private FlowStartNodeEvent flowStartEvent;
	@Override
	public void process(FlowWork flowWork, FlowModel flowModel, BaseModel currentNddeModel,FlowParam flowParam) {
		// Start node event --
		if(null != flowStartEvent) {
			StartModel startModel = flowModel.getStartModel();
			flowStartEvent.onEvent(flowWork, startModel, flowParam);
		}
	}
	@Override
	public String getNodeType(a) {
		return "START"; }}Copy the code
  • TaskNodeProcessorImpl.java
package com.mole.modules.flow.processor.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mole.framework.base.ServiceException;
import com.mole.framework.base.YesNoEnum;
import com.mole.framework.util.JsonUtil;
import com.mole.framework.util.StringUtil;
import com.mole.modules.flow.entity.FlowTask;
import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.enums.FlowErrEnum;
import com.mole.modules.flow.event.FlowTaskCreatedEvent;
import com.mole.modules.flow.event.FlowTaskNodeEvent;
import com.mole.modules.flow.mapper.FlowTaskMapper;
import com.mole.modules.flow.mapper.FlowWorkMapper;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.model.TaskModel;
import com.mole.modules.flow.param.FlowParam;
import com.mole.modules.flow.processor.FlowNodeProcessor;
import com.mole.modules.flow.service.FlowNodeUserService;
/** * Task node processor *@author MLD
 *
 */
@Component
public class TaskNodeProcessorImpl implements FlowNodeProcessor {
	@Autowired
	private FlowWorkMapper flowWorkMapper;
	@Autowired
	private FlowTaskMapper flowTaskMapper;
	@Autowired(required=false)
	private FlowNodeUserService flowNodeUserService;
	@Autowired(required=false)
	private FlowTaskNodeEvent flowTaskStartEvent;
	@Autowired(required=false)
	private FlowTaskCreatedEvent flowTaskCreatedEvent;
	@Override
	public void process(FlowWork flowWork, FlowModel flowModel, BaseModel currentNddeModel,FlowParam flowParam) {
		// Next node
		// BaseModel nextNodeModel = flowModel.getNodeModel(flowWork.getNextNodeId());
		TaskModel taskModel = (TaskModel)currentNddeModel;
		1. Create a task
		createTask(flowWork, flowParam, taskModel);
		// 2. Modify the current process instance status
		if(null != flowTaskStartEvent) {
			flowTaskStartEvent.OnEvent(flowWork, taskModel, flowParam);
		}
	}
	/** * Create task *@param flowWork
	 * @param param
	 * @param taskModel
	 */
	private int createTask(FlowWork flowWork,FlowParam flowParam,TaskModel taskModel){
		Date now = new Date();
		String flowWorkId = flowWork.getId();
		List<String> userIds = flowParam.getUserIds();
		if(null==userIds) {
			userIds = new ArrayList<>();
		}
		if(null! =flowNodeUserService) { List<String> list = flowNodeUserService.loadUser(taskModel, flowParam);if(null! =list) {for(String id:list) {
					if(! userIds.contains(id)) { userIds.add(id); }}}}if(userIds.isEmpty()) {
			throw new ServiceException(FlowErrEnum.FLOW86000009);
		}
		for(String actorUserId:userIds) {
			// create a task
			FlowTask flowTask = new FlowTask();
			flowTask.setCreateTime(now);
			flowTask.setFlowWorkId(flowWorkId);
			flowTask.setIsDeleted(YesNoEnum.NO);
			flowTask.setServiceId(flowParam.getServiceId());
			flowTask.setServiceType(flowParam.getServiceType());
			flowTask.setTaskName(taskModel.getName());
			flowTask.setUpdateTime(now);
			flowTask.setOperator(flowParam.getOperator());
			flowTask.setStatus(FlowTask.StatusEnum.CREATED);
			flowTask.setFlowNodeId(taskModel.getId());
			flowTask.setActorUserId(actorUserId);
			flowTask.setFlowParam(JsonUtil.toJson(flowParam));
			if(StringUtil.isNotEmpty(flowParam.getTitle())) {
				flowTask.setTaskName(flowParam.getTitle());
			}
			flowTaskMapper.insertSelective(flowTask);
			if(null != flowTaskCreatedEvent) {
				flowTaskCreatedEvent.onEvent(flowWork, flowTask, flowParam);
			}
		}
		return userIds.size();
	}

	@Override
	public String getNodeType(a) {
		return "TASK"; }}Copy the code
  • EndNodeProcessorImpl.java
package com.mole.modules.flow.processor.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.event.FlowEndNodeEvent;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.EndModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.param.FlowParam;
import com.mole.modules.flow.processor.FlowNodeProcessor;
@Component
public class EndNodeProcessorImpl implements FlowNodeProcessor{
	@Autowired(required=false)
	private FlowEndNodeEvent flowEndEvent;
	@Override
	public void process(FlowWork flowWork, FlowModel flowModel, BaseModel currentNddeModel,FlowParam flowParam) {
		if(null== flowEndEvent) { EndModel endModel = flowModel.getEndModel(); flowEndEvent.onEvent(flowWork, endModel, flowParam);; }}@Override
	public String getNodeType(a) {
		return "END"; }}Copy the code

Node processor control class

The node processor controls the class that calls the processor uniformly

  • FlowNodeProcesstorCtl.java
package com.mole.modules.flow.processor;

import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.mapper.FlowWorkMapper;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.EndModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.model.StartModel;
import com.mole.modules.flow.param.FlowParam;
/** * node processor controller *@author MLD
 *
 */
@Component
public class FlowNodeProcesstorCtl {
	@Autowired(required=false)
	private List<FlowNodeProcessor> flowNodeProcessorList;
	@Autowired
	private FlowWorkMapper flowWorkMapper;
	/** * node process processor control *@paramFlowWork Current flow instance *@paramFlowModel Current process instance model *@paramModel Current node model *@paramParam Process parameters */
	public void process(FlowWork flowWork,FlowModel flowModel, BaseModel model,FlowParam param){
		FlowWork upFlowWork = new FlowWork();
		if(model == null) {
			return;
		}
		// Execute the start node
		for(FlowNodeProcessor processor:flowNodeProcessorList) {
			if(processor.getNodeType().equals(model.getNodeType())){
				BaseModel nextNodeModel = flowModel.getNodeModel(model.getNextNodeId());
				if(model instanceof StartModel) { // Start node
					// Execute the start model
					processor.process(flowWork, flowModel, model,param);
					// Execute the next model
					process(flowWork,flowModel,nextNodeModel, param);
				} else if(model instanceof EndModel)  {
					// End the node
					upFlowWork.setStatus(FlowWork.StatusEnum.END);
					Date now = new Date();
					upFlowWork.setId(flowWork.getId());
					upFlowWork.setCurrentNodeId(model.getId());
					upFlowWork.setNextNodeId("");
					upFlowWork.setLastOperator(param.getOperator());
					upFlowWork.setUpdateTime(now);
					flowWorkMapper.updateByPrimaryKeySelective(upFlowWork);
				} else { // Non-start and end nodes
					processor.process(flowWork, flowModel, model,param);
					Date now = new Date();
					upFlowWork.setId(flowWork.getId());
					upFlowWork.setCurrentNodeId(model.getId());
					upFlowWork.setNextNodeId(model.getNextNodeId());
					upFlowWork.setLastOperator(param.getOperator());
					upFlowWork.setUpdateTime(now);
					flowWorkMapper.updateByPrimaryKeySelective(upFlowWork);
				}
				// There is only one of the same type.
				break; }}}}Copy the code

Node event interface class

Node event interface, implemented by business system

  • FlowStartNodeEvent
package com.mole.modules.flow.event;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.model.StartModel;
import com.mole.modules.flow.param.FlowParam;
/** * Process instance start event - implemented by business system *@author MLD
 *
 */
public interface FlowStartNodeEvent {
	/ * * *@paramFlowWork Current flow instance *@paramStartModel Start node model *@paramParam Process parameters */
	public void onEvent(FlowWork flowWork, StartModel startModel,FlowParam flowParam);
}

Copy the code
  • FlowTaskNodeEvent.java
package com.mole.modules.flow.event;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.model.TaskModel;
import com.mole.modules.flow.param.FlowParam;

/** * Task start event - implemented by business system *@author MLD
 *
 */
public interface FlowTaskNodeEvent {
	/** * Task start event *@paramFlowWork Current flow instance *@paramTaskModel Task node model *@paramFlowParam Process parameters */
	public void OnEvent(FlowWork flowWork,TaskModel taskModel,FlowParam flowParam);
}	

Copy the code
  • FlowTaskCreatedEvent.java
package com.mole.modules.flow.event;

import com.mole.modules.flow.entity.FlowTask;
import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.param.FlowParam;

/** * Create task event - implemented by business system *@author MLD
 *
 */
public interface FlowTaskCreatedEvent {
	/ * * * *@paramFlowWork Current flow instance *@paramFlowTask Current task instance *@paramFlowParam Process parameters */
	public void onEvent(FlowWork flowWork,FlowTask flowTask,FlowParam flowParam);
}

Copy the code
  • FlowEndNodeEvent.java
package com.mole.modules.flow.event;

import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.model.EndModel;
import com.mole.modules.flow.param.FlowParam;
/** * Process instance end event - implemented by business system *@author MLD
 *
 */
public interface FlowEndNodeEvent {
	/ * * * *@paramFlowWork Current flow instance *@paramEndModel End node *@paramFlowParam Process parameters */
	public void onEvent(FlowWork flowWork,EndModel endModel, FlowParam flowParam);
}

Copy the code

Custom task node participant interface class

package com.mole.modules.flow.service;

import java.util.List;

import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.param.FlowParam;
/** * Custom node participant interface - implemented by business system *@author MLD
 *
 */
public interface FlowNodeUserService {
	/** * Load node participant *@paramNodeModel nodeModel *@paramParam Process parameters *@return* /
	public List<String> loadUser(BaseModel nodeModel,FlowParam param);
}

Copy the code

Flow control class

Classes provided to the business operation process

  • FlowWorkCtl.java
package com.mole.modules.flow;

import java.io.IOException;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import tk.mybatis.mapper.entity.Condition;

import com.mole.framework.base.ServiceException;
import com.mole.framework.base.YesNoEnum;
import com.mole.framework.util.JsonUtil;
import com.mole.modules.flow.entity.FlowDefine;
import com.mole.modules.flow.entity.FlowTask;
import com.mole.modules.flow.entity.FlowWork;
import com.mole.modules.flow.enums.FlowErrEnum;
import com.mole.modules.flow.mapper.FlowDefineMapper;
import com.mole.modules.flow.mapper.FlowTaskMapper;
import com.mole.modules.flow.mapper.FlowWorkMapper;
import com.mole.modules.flow.model.BaseModel;
import com.mole.modules.flow.model.FlowModel;
import com.mole.modules.flow.model.StartModel;
import com.mole.modules.flow.param.FlowParam;
import com.mole.modules.flow.parser.FlowNodeParser;
import com.mole.modules.flow.processor.FlowNodeProcesstorCtl;

/** * Process control class - engine *@author MLD
 *
 */
@Component
public class FlowWorkCtl {
    /** * Start the process through the process definition code *@paramFlowDefineCode Process definition code *@paramParam process starts the entry parameter */
	@Autowired
	private FlowDefineMapper flowDefineMapper;
	@Autowired
	private FlowNodeParser flowNodeParser;
	@Autowired
	private FlowWorkMapper flowWorkMapper;
	@Autowired
	private FlowNodeProcesstorCtl flowNodeProcesstorCtl;
	@Autowired
	private FlowTaskMapper flowTaskMapper;
	/** * Start a process *@param flowDefineCode
	 * @paramOperator Operator User ID *@param param
	 */
    public FlowWork startProcess(String flowDefineCode,String operator,FlowParam param){
        // TODO load process configuration
    	FlowDefine q = new FlowDefine();
    	q.setFlowDefineCode(flowDefineCode);
    	q.setIsDeleted(YesNoEnum.NO);
    	if(null==param.getOperator()) {
    		param.setOperator(operator);
    	}
    	FlowDefine flowDefine = flowDefineMapper.selectOne(q);
    	if(null == flowDefine) {
    		// The process definition does not exist
    		throw new ServiceException(FlowErrEnum.FLOW86000001);
    	}
    	FlowModel flowModel = null;
    	try {
    		flowModel = flowNodeParser.parser(flowDefine.getFlowDefine());
		} catch (IOException e) {
			// Process parsing exception
    		throw new ServiceException(FlowErrEnum.FLOW86000002);
		}
    	StartModel startModel = flowModel.getStartModel();
    	Date now = new Date();
    	// Create a new process
		FlowWork flowWork = new FlowWork();
		flowWork.setCreateTime(now);
		flowWork.setCurrentNodeId(startModel.getId());
		flowWork.setFlowDefine(flowDefine.getFlowDefine());
		flowWork.setFlowName(flowDefine.getFlowName());
		flowWork.setIsDeleted(YesNoEnum.NO);
		flowWork.setNextNodeId(startModel.getNextNodeId());
		flowWork.setStatus(FlowWork.StatusEnum.START);
		flowWork.setUpdateTime(now);
		flowWork.setFlowParam(JsonUtil.toJson(param));
		flowWork.setFlowDefineCode(flowDefineCode);
		flowWork.setLastOperator(operator);
		flowWorkMapper.insertSelective(flowWork);
		// Execute the start model
		flowNodeProcesstorCtl.process(flowWork, flowModel, startModel, param);
		return flowWork;
    }
    /** * Complete a task *@paramFlowTaskId Process task ID *@paramOperator Operator User ID *@paramParam Process parameters */
    public void completeTask(String flowTaskId,String operator,FlowParam param) {
    	if(null==param.getOperator()) {
    		param.setOperator(operator);
    	}
    	FlowTask task = flowTaskMapper.selectByPrimaryKey(flowTaskId);
		if(null == task || YesNoEnum.YES.equals(task.getIsDeleted())) {
			// The process task does not exist
			throw new ServiceException(FlowErrEnum.FLOW86000007);
		}
		// Determine whether the operator is in the assigned user
		if(! task.getActorUserId().equals(operator)) {// The user has no process task
			throw new ServiceException(FlowErrEnum.FLOW86000008);
		}
		if(FlowTask.StatusEnum.FINISHED.equals(task.getStatus())) {
			// This is done
			return;
		}
		FlowWork flowWork = flowWorkMapper.selectByPrimaryKey(task.getFlowWorkId());
		if(null==flowWork) {
			// The process instance does not exist
			throw new ServiceException(FlowErrEnum.FLOW86000005);
		}
		FlowModel flowModel = null;
    	try {
    		flowModel = flowNodeParser.parser(flowWork.getFlowDefine());
		} catch (IOException e) {
			// Process parsing exception
    		throw new ServiceException(FlowErrEnum.FLOW86000002);
		}
		BaseModel currentNodeModel = flowModel.getNodeModel(task.getFlowNodeId());
    	BaseModel nextNodeModel = flowModel.getNodeModel(currentNodeModel.getNextNodeId());
		Date now = new Date();
		FlowTask upTask = new FlowTask();
		upTask.setId(task.getId());
		upTask.setUpdateTime(now);
		upTask.setStatus(FlowTask.StatusEnum.FINISHED);
		upTask.setFinishTime(now);
		// Change the current task to completed
		flowTaskMapper.updateByPrimaryKeySelective(upTask);
		int count = 0;
		if("ALL".equals(currentNodeModel.getNodeType())) {
			// Only when everyone has finished can we proceed to the next step
			Condition qTaskCondition = new Condition(FlowTask.class);
			qTaskCondition.createCriteria().andEqualTo("flowWorkId", task.getFlowWorkId())
			.andEqualTo("flowNodeId", task.getFlowNodeId())
			.andEqualTo("status", FlowTask.StatusEnum.CREATED)
			.andEqualTo("isDeleted", YesNoEnum.NO)
			.andNotEqualTo("actorUserId", operator);
			count = flowTaskMapper.selectCountByCondition(qTaskCondition);
		} else {
			// If the ANY task is completed, the next step can be taken. If the other tasks are cancelled, the next step can be taken
			Condition upTaskCondition = new Condition(FlowTask.class);
			upTaskCondition.createCriteria().andEqualTo("flowWorkId", task.getFlowWorkId())
			.andEqualTo("flowNodeId", task.getFlowNodeId())
			.andEqualTo("status", FlowTask.StatusEnum.CREATED)
			.andEqualTo("isDeleted", YesNoEnum.NO)
			.andNotEqualTo("actorUserId", operator);
			FlowTask upTaskToCancel = new FlowTask();
			upTaskToCancel.setUpdateTime(now);
			upTaskToCancel.setStatus(FlowTask.StatusEnum.CANCEL);
			flowTaskMapper.updateByPrimaryKeySelective(upTaskToCancel);
		}
		if (count==0) {
			// 3. Go to the next stepflowNodeProcesstorCtl.process(flowWork, flowModel, nextNodeModel, param); }}/** * Reject a task *@paramFlowTaskId Process task ID *@paramOperator Operator User ID *@paramFlowParam Process parameters */
    public void rejectTask(String flowTaskId,String operator,FlowParam flowParam) {
    	if(null==flowParam.getOperator()) {
    		flowParam.setOperator(operator);
    	}
    	FlowTask task = flowTaskMapper.selectByPrimaryKey(flowTaskId);
		if(null == task || YesNoEnum.YES.equals(task.getIsDeleted())) {
			// The process task does not exist
			throw new ServiceException(FlowErrEnum.FLOW86000007);
		}
		// Check if the person is inside
		if(! task.getActorUserId().equals(operator)) {// The user has no process task
			throw new ServiceException(FlowErrEnum.FLOW86000008);
		}
		if(FlowTask.StatusEnum.FINISHED.equals(task.getStatus())) {
			// This is done
			return;
		}
		FlowWork flowWork = flowWorkMapper.selectByPrimaryKey(task.getFlowWorkId());
		if(null==flowWork) {
			// The process instance does not exist
			throw new ServiceException(FlowErrEnum.FLOW86000005);
		}
		FlowModel flowModel = null;
    	try {
    		flowModel = flowNodeParser.parser(flowWork.getFlowDefine());
		} catch (IOException e) {
			// Process parsing exception
    		throw new ServiceException(FlowErrEnum.FLOW86000002);
		}
		Date now = new Date();
		FlowTask upTask = new FlowTask();
		upTask.setId(task.getId());
		upTask.setUpdateTime(now);
		upTask.setStatus(FlowTask.StatusEnum.REJECT);
		upTask.setFinishTime(now);
		// Change the current task to Reject
		flowTaskMapper.updateByPrimaryKeySelective(upTask);
		// Interrupts the current node task
		Condition upTaskCondition = new Condition(FlowTask.class);
		upTaskCondition.createCriteria().andEqualTo("flowWorkId", task.getFlowWorkId())
		.andEqualTo("flowNodeId", task.getFlowNodeId())
		.andEqualTo("status", FlowTask.StatusEnum.CREATED)
		.andEqualTo("isDeleted", YesNoEnum.NO)
		.andNotEqualTo("actorUserId", operator);
		FlowTask upTaskToCancel = new FlowTask();
		upTaskToCancel.setUpdateTime(now);
		upTaskToCancel.setStatus(FlowTask.StatusEnum.CANCEL);
		flowTaskMapper.updateByPrimaryKeySelective(upTaskToCancel);
		
		// Get the last node
    	BaseModel preNodeModel = flowModel.getPreNodeModel(task.getFlowNodeId());
    	// Execute the last node processorflowNodeProcesstorCtl.process(flowWork, flowModel, preNodeModel, flowParam); }}Copy the code

Call the sample

  • Start a process instance
String flowDefineCode = "yzq_invite_bid";
FlowParam flowParam = new FlowParam();
flowParam.setServiceId(UUID.randomUUID().toString());
flowParam.setServiceType("yzq_invite_bid");
flowParam.setUserIds(new ArrayList<>());
flowParam.getUserIds().add("1");
Sting operator = "1";
flowWorkCtl.startProcess(flowDefineCode,operator, flowParam);
Copy the code
  • Complete a task
String flowTaskId = "flowTaskId";
FlowParam flowParam = new FlowParam();
flowParam.setServiceId(UUID.randomUUID().toString());
flowParam.setServiceType("yzq_invite_bid");
flowParam.setUserIds(new ArrayList<>());
flowParam.getUserIds().add("1");
Sting operator = "1";
flowWorkCtl.completeTask(flowTaskId, operator, flowParam);
Copy the code
  • To reject the task
String flowTaskId = "flowTaskId";
FlowParam flowParam = new FlowParam();
flowParam.setServiceId(UUID.randomUUID().toString());
flowParam.setServiceType("yzq_invite_bid");
flowParam.setUserIds(new ArrayList<>());
flowParam.getUserIds().add("1");
Sting operator = "1";
flowWorkCtl.rejectTask(flowTaskId, operator, flowParam);
Copy the code

The frame used in this project

  • Springboot 2.0
  • tk.mybatis

other

I would like to open source, but the coupling between the project and the framework of the company is too severe, so it is not convenient to pull out. When I have time, I will consider pulling out this part of the code separately from the source. Another point is that only the simplest sequential processes are being considered, and complex processes involving conditions, branches, merges, subprocesses and so on have not been considered for the time being. But the intention is just to step by step, slowly analyze the principle.