什么是工作流/工作引擎?
Georgakopoulos给出的工作流定义
是:工作流是将一组任务组织起来以完成某个经营过程:定义了任务的触发顺序和触发条件,每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以由一个或多个人与软件系统协作完。它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。工作流管理系统的目标:管理工作的流程以确保工作在正确的时间被期望的人员所执行——在自动化进行的业务过程中插入任何的执行和干预。所谓工作流引擎
是指workflow作为应用系统的一部分,并为之提供对各应用系统有决定作用的根据角色、分工和条件的不同决定信息传递路由、内容等级等核心解决方案。例如开发一个系统最关键的部分不是系统的界面,也不是和数据库之间的信息交换,而是如何根据业务逻辑开发出符合实际需要的程序逻辑并确保其稳定性、易维护性和弹性。比如你的系统中有一个任务流程,一般情况下这个任务的代码逻辑、流程你都要自己来编写。实现它是没有问题的。但是谁能保证逻辑编写的毫无纰漏?经过无数次的测试与改进,这个流程没有任何漏洞也是可以实现的,但是明显就会拖慢整个项目的进度。
BPMN2.0规范
BPMN(Business Process Model and Notation)--业务流程模型与符号。
BPMN是一套流程建模的标准,主要目标是被所有业务用户容易理解的符号,支持从创建流程轮廓的业务分析到这些流程的最终实现,知道最终用户的管理监控。
通俗一点其实就是一套规范,画流程模型的规范。流程模型包括:流程图、协作图、编排图、会话图。
什么是
Activiti
?
Activiti的创始人也就是JBPM(也是一个优秀的BPM引擎)的创始人,从Jboss离职后开发了一个新的BPM引擎:Activiti。所以,Activiti有很多地方都有JBPM的影子。Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于 Java 的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。ProcessEngine
对象,这是Activiti工作的核心。负责生成流程运行时的各种实例及数据、监控和管理流程的运行。所有的操作都是从获取引擎开始的,所以一般会把引擎作为全局变量。
ProcessEngine processEngine = ProcessEngine.getDefaultProcessEngine();
为什么要用工作流引擎?
简单来说,就是为了统一管理流程业务。想想看,如果要设计一个流程的程序,通常需要在数据库中存各种状态值,比如一个订单程序,要标记订单是未付款、已付款、已出库等等状态,而这些各种各样的状态参杂在程序中,逻辑自然就变得复杂了。而将这些状态对应流程里的一个个步骤,交由流程引擎去管理,这样不仅简化了业务逻辑代码,而且,还有很强的扩展性。我可以修改我的流程,我可以添加一些步骤而不用改我的数据库表结构,不用改我的业务逻辑。
在Eclipse中使用Activiti
推荐使用离线安装
:断网
通过网盘:https://pan.baidu.com/s/15jZrf5LzlrgG82gPAh3cng 密码:4kxd ,下载在离线安装包。
将下载好的jars文件夹里的3个jar文件复制到eclipse安装目录的plugins目录下。
删除eclipse安装目录下,configuration文件夹里的org.eclipse.update文件夹,重启eclipse。
打开eclipse,在Help->Install New Software后的弹出窗点击Available Software Sites,删除设置过的Activiti资源信息,add确认安装。
在IDEA中使用Activiti
- 点击菜单【File】-->【Settings...】打开【Settings】窗口。
点击左侧【Plugins】按钮,在右侧输出"actiBPM",点击下面的【Search in repositories】链接会打开【Browse Repositories】窗口
进入【Browse Repositories】窗口,选中左侧的【actiBPM】,点击右侧的【Install】按钮,开始安装。
安装完成后,会提示【Restart IntelliJ IDEA】,重启IDEA即可完成安装。
如何更优雅地使用Activiti
随着微服务的普及,Rest风格的编码规范越来被推崇,因而我们使用Activiti Rest模块进行Activiti统一Rest调用。
1. Activiti REST模块介绍
Activiti-rest是Activiti提供的一组可以直接操作工作流引擎的REST API,使用者可以在自己应用中直接调用Activiti-rest接口。
1.1 使用REST的好处
简单化:利用现有模块(activiti-rest.war)代替直接API调用
标准化:各个系统根据rest模块的接口规范访问REST资源,统一处理;对于工作流平台来说此特性尤为突出
扩展性:如果官方提供的REST接口还不能满足可以继续在其基础上进行扩展以满足业务系统(平台)的需求
1.2 不适合使用REST的场景
业务数据与流程数据分离:就像kft-activiti-demo中普通表单的演示一样,业务数据保存在一张单独设计的表中,而不是把表单数据保存在引擎的变量表中,所以对于这样的场景中需要联合事务管理的就不能使用REST了,例如:启动流程、任务完成、业务与流程数据联合查询。
1.3 部署Rest模块
从5.11版本开始不再使用ant脚本的方式启动demo,并且把activiti-explorer和activiti-rest分离并分别提供一个war包,在wars目录可以找到它。
把activiti-rest.war解压到Web服务器的应用部署目录(例如tomcat的webapps),根据实际需求修改activiti-rest/WEB-INF/classes/db.properties里面的数据库配置后启动应用。
可以通过REST工具测试是否部署成功可以正常的提供服务,例如Chrome的插件REST Console,或者通过Spring MVC提供的RestTemplate。
2. 访问REST资源
对于REST模块提供的接口可以参考用户手册的REST API章节,有着详细的介绍(包括URL和参数含义)。
2.1 身份认证
REST接口的大部分功能都需要验证,默认使用Basic Access Authentication(基本连接认证),所以在访问资源时要在header中添加验证信息,当然为了安全期间把用户名和密码进行base 64位加密。
可以在用户登陆之后把用户名和密码进行加密并设置到session中,这样在前端就可以直接通过Ajax方式获取资源了:
import jodd.util.Base64;
String base64Code = "Basic " + Base64.encodeToString(user.getId() + ":" + user.getPassword());
session.setAttribute("BASE_64_CODE", base64Code);
2.2 通过Ajax方式读取资源
下面通过kft-activiti-demo中的代码片段介绍:
$.ajax({
type: "get",
url: REST_URL + 'process-definition/' + processDefinitionId + '/form',
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', BASE_64_CODE);
},
dataType: 'html',
success: function(form) {
// 获取的form是字符行,html格式直接显示在对话框内就可以了,然后用form包裹起来
$(dialog).html(form).wrap("
在第5行处设置了ajax请求的header信息,这样REST模块就可以通过header的信息进行身份认证,通过之后就可以执行资源请求并返回处理结果。
2.3 通过Java方式读取资源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
public class TestLogin {
public static void main(String[] args) throws Exception, IOException {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost postRequest = new HttpPost("http://localhost:8080/activiti-rest/service/login");
StringEntity input = new StringEntity("{\"userId\":\"kermit\",\"password\":\"kermit\"}");
input.setContentType("application/json");
postRequest.setEntity(input);
HttpResponse response = httpClient.execute(postRequest);
BufferedReader br = new BufferedReader(new InputStreamReader((response.getEntity().getContent())));
String output;
System.out.println("Output from Server .... \n");
while ((output = br.readLine()) != null) {
System.out.println(output);
}
httpClient.getConnectionManager().shutdown();
}
}
3. Ajax跨域问题解决办法
把REST模块和应用部署在同一个Web服务器中(废话……)
等待官方提供JSONP的支持,JIRA Issue:ACT-1534
利用后台代理方式,把请求的URL发送给后台代理服务器,获取数据之后再把结果返回给前台
在WEB界面绘制流程图
Activiti Modeler 组件,是一套支持WEB界面实现流程绘制的服务组件,允许使用方自定义流程。
1.下载 Activiti-activiti-5.22.0 包
下载地址:https://github.com/Activiti/Activiti/tree/activiti-5.22.0/
- 1
提取 Activiti-activiti-5.22.0 包内容,放置到项目中
1.1.1 从 Activiti-activiti-5.22.0>modules>activiti-webapp-explorer2>src>main>webapp 下提取diagram-viewer,editor-app,modeler.html 放置到 resource/static 中1.1.2 从Activiti-activiti-5.22.0>modules>activiti-webapp-explorer2>src>main>resources 下提取stencilset.json 放置到 resource/static 中1.1.3 从Activiti-activiti-5.22.0>modules>activiti-modeler>src>main>java>org>activiti>rest>editor 下提取 ModelEditorJsonRestResource.java,ModelSaveRestResource.java ,StencilsetRestResource.java放置到项目中1.1.4 修改ModelEditorJsonRestResource.java
@RestController
@RequestMapping(value = "/service") //添加
1.1.5 修改ModelSaveRestResource.java
@RestController
@RequestMapping("/service")
public class ModelSaveRestResource implements ModelDataJsonConstants {
protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private ObjectMapper objectMapper;
/**
* 保存模型
* @param modelId
* @param json_xml
* @param svg_xml
* @param name
* @param description
*/
@RequestMapping(value = "/model/{modelId}/save", method = RequestMethod.PUT)
public void saveModel(@PathVariable String modelId, @RequestParam String json_xml,
@RequestParam String svg_xml,@RequestParam String name, @RequestParam String description) {
try {
// 获取模型信息并更新模型信息
ObjectMapper objectMapper = new ObjectMapper();
Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(MODEL_NAME, name);
modelJson.put(MODEL_DESCRIPTION, description);
model.setMetaInfo(modelJson.toString());
model.setName(name);
repositoryService.saveModel(model);
repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));
// 基于模型信息做流程部署
InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes("utf-8"));
TranscoderInput input = new TranscoderInput(svgStream);
PNGTranscoder transcoder = new PNGTranscoder();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
transcoder.transcode(input, output);
final byte[] result = outStream.toByteArray();
repositoryService.addModelEditorSourceExtra(model.getId(), result);
outStream.close();
Model modelData = repositoryService.getModel(modelId);
ObjectNode modelNode = (ObjectNode) objectMapper.readTree(repositoryService.getModelEditorSource(modelData.getId()));
byte[] bpmnBytes = null;
BpmnModel model2 = new BpmnJsonConverter().convertToBpmnModel(modelNode);
bpmnBytes = new BpmnXMLConverter().convertToXML(model2);
String processName = modelData.getName() + ".bpmn";
Deployment deployment = repositoryService.createDeployment()
.name(modelData.getName())
.addString(processName, StringUtils.toEncodedString(bpmnBytes, Charset.forName("UTF-8")))
.deploy();
} catch (Exception e) {
LOGGER.error("模型保存失败", e);
throw new ActivitiException("模型保存失败", e);
}
}
}
1.1.6 修改StencilsetRestResource.java
@RestController
@RequestMapping(value = "/service")
public class StencilsetRestResource {
/**
* 加载模板集配置
* @return
*/
@RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
public @ResponseBody String getStencilset() {
try {
InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("static/stencilset-cn.json");
return IOUtils.toString(stencilsetStream, "utf-8");
} catch (Exception e) {
throw new ActivitiException("加载模板集配置失败", e);
}
}
}
1.1.7 修改app-cfg.js
ACTIVITI.CONFIG = {
// 'contextRoot' : '/activiti-explorer/service',
'contextRoot' : '/activiti/service',
}
- 2
activiti 绘制器的汉化
使用汉化的stencilset.json替换resource/static 下的stencilset.json
- 3
实现控制类,用于模型的构建
创建ActivitiModelController.java
@RestController
public class ActivitiModelController {
public static Logger logger = Logger.getLogger(ActivitiModelController.class);
@Autowired
private ProcessEngine processEngine; // 定义流程引擎
@Autowired
private ObjectMapper objectMapper;
private final static Integer MODEL_REVISION = 1; // 模型的版本
private final static String ACTIVITI_REDIRECT_MODELER_INDEX = "/activitiService/modeler.html?modelId="; // modeler.html页面地址
private final static String ACTIVITI_NAMESPACE_VALUE = "http://b3mn.org/stencilset/bpmn2.0#"; // 默认的空间值
private final static String ACTIVITI_ID_VALUE = "canvas"; // 默认ID值
/**
* 新建一个模型
* @param response
* @throws IOException
*/
@RequestMapping("/activiti-ui.html")
public void newModel(HttpServletResponse response) throws IOException {
RepositoryService repositoryService = processEngine.getRepositoryService();
// 初始化空的流程资源模型,填充信息并持久化模型
Model model = repositoryService.newModel();
String uuid = UUUID.randomUUID().toString().replaceAll("-", "");
ObjectNode modelNode = objectMapper.createObjectNode();
modelNode.put(ModelDataJsonConstants.MODEL_NAME, uuid);
modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, "");
modelNode.put(ModelDataJsonConstants.MODEL_REVISION, MODEL_REVISION);
model.setName(uuid);
model.setKey(uuid);
model.setMetaInfo(modelNode.toString());
repositoryService.saveModel(model);
// 将 editorNode 数据填充到模型中, 并做页面的重定向
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", ACTIVITI_ID_VALUE);
editorNode.put("resourceId", ACTIVITI_ID_VALUE);
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace",ACTIVITI_NAMESPACE_VALUE);
editorNode.put("stencilset", stencilSetNode);
repositoryService.addModelEditorSource(model.getId(),editorNode.toString().getBytes("utf-8"));
response.sendRedirect(ACTIVITI_REDIRECT_MODELER_INDEX + model.getId());
}
}
- 4
验证 activiti modeler
http://ip:port/activiti/activiti-ui.html
搭建工作流平台
参考开源社区: