图1:一个典型的审批工作流程
最近做了一次对企业/云平台级工作流引擎Activiti的调查:
InfoQ上2012年4月就已有过一篇推荐Activiti的文章,本文则是笔者近2个月实战Activiti的一篇简书&共享。
打通基于REST API接口的 工作流生命周期(不足部分做定制补充)
不包括
调研、定制中以下一些资料较为有用,(感谢相关作者&)已有相关基础的读者可据此推断本文的参考价值。
如果希望直接将Demo跑起来边看边理解的话,可以跳到下一节,否则就先随我把概念部分混个眼熟。
制作了一张工作流在生命周期中不同阶段/环节间切换的关系图:
图2:工作流生命周期
先上一张诺贝尔颁奖盛典筹备工作的设计模型图:
图3:The Nobel Prize Process Diagram
基于工业标准的工作流设计,是满足不同领域客户复杂业务需求的有力保障。
BPMN2.0标准工作流的模型编辑与保存,可以在浏览器App形式的Activiti Modeler中进行(并可以嵌入你的业务系统中),在此直接对照着Modeler的图标对主要的BPMN元素做简单介绍:
图4:Activiti Modeler Shape Repository -- BPMN2.0
开始事件(Start Event)
<提醒>
initiator
属性中所保存的、只是一个“用于记录启动者user_id的变量名称”,可以算是个“变量声明”属性。当一个用户、例如"kermit"(官方范例用户)、启动了这一工作流时(在流程设计模型被部署、成为一个“流程定义”后、它就能被某一用户启动成为一个“流程实例”),"kermit"就会被记录到在initiator
中声明的变量、比如"starter"中,那样就会有一个流程变量starter被赋值为"kermit",并能在后续的活动节点中(e.g.用户任务)被用于任务办理人(assignee)属性的赋值表达式(e.g.${starter}
)、或是其他的例如分歧条件表达式中(${starter=="kermit"}
)。详见官方文档。initiator
属性是唯一用于保存“变量名称”的,在整个流程级别、或是任务级别、都存在着类似于candidateUsers
/candidateStarterUsers
的属性,它们保存的直接就是允许启动流程/办理任务的用户ID(允许多个)。详见官方文档。candidateStarterUsers
/candidateStarterGroups
(允许启动者),Eclipse插件Activiti Designer中可以,但仅供开发者使用;因此需要通过扩展REST API、在“流程设计模型::部署”、或是“流程定义::设置”环节进行设置。活动节点(Activity)
<提醒>
humanPerformer
vs activiti:candidateUsers
:这里将引入BPMN标准属性与Activiti扩展属性的对比与互换的话题。
...
user(kermit), group(management)
b. Activiti扩展属性
...
2.标准与扩展冲突的解决:详见官方文档
a.Activiti扩展的前提是:总有简单的方法、转换成标准方法
b.Activiti扩展的目标是:最终把它们加入到下一版本的BPMN规范中, 或者至少可以引起对特定BPMN结构的讨论
结构(Structural)
<非重点>
网关(Gateway)
<非重点>
结束事件(End Event)
<非重点>
连接(Connecting Object)
100 && order.price < 250}]]>
注意:目前条件表达式只能使用UEL(统一表达式语言),详见官方文档。
我所使用的开发环境:
<提醒>
Maven的基本配置对初学者来说会多有挫折
Maven本身是个好东西,开源项目很多对其他开源项目有大量的依赖引用,难免存在交叉引用、版本覆盖、重复下载等繁琐问题,而Maven工具&标准就是为了应对这个而产生的(并成为Apache顶级项目)。
可是,主要陷阱在于:
解决方法:
必须读
重新设置本地仓库位置,使得你可以更方便地 a.将需要手动下载的依赖库放置其中 b.备份已经成功构建的本地仓库、用于环境移植不必读
通常是根据项目不同,修改项目pom.xml文件中的不必读
我们需要的是导入Activiti官方项目、而非新建,具体方法见后“导入已有项目”。不必读
导入项目的pom.xml中已经配置好了,除非你有新的依赖包需求。Eclipse中导入的方法:File > Import... > Maven::Existing Maven Projects > Next > 选中项目根目录下的pom.xml文件即可。
Maven配置修改(pom.xml文件编辑):m2eclipse插件已经装好的情况下,pom.xml会自动被在表单编辑器中打开(不过通常只需在文本方式下编辑、表单方式下辅助阅读)
a. 添加中央仓库镜像,比如咖啡兔的Maven私服(不过有时也会断线、需添加其他镜像)
......
kafeitu
http://maven.kafeitu.me/nexus/content/groups/public
b. 添加依赖库,比如数据库驱动(这里不需要修改,使用默认h2数据库即可、且无需下载安装h2(已自带,否则反而要改版本号))
......
com.h2database
h2
数据库连接修改:src/main/resources/db.properties
(可选)给出一个改用Microsoft SQL Server的例子:
db=mssql
jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=activiti
jdbc.username=sa
jdbc.password=
不过相应的,pom.xml中的数据库驱动依赖库就也需要添改:
......
com.microsoft.sqlserver
sqljdbc4
工作流引擎配置修改:src/main/webapp/WEB-INF/activiti-standalone-context.xml
......
......
Activiti工作流引擎配置、目前(5.16.3)采用Spring框架方式加载,以实现不同扩展的灵活配置。上手阶段所需做的修改,主要就是如上属性(分别用于自动生成Demo数据,以及比对引擎与数据库之间的版本、自动新建/升级数据库),更多可参考官方文档。
Maven项目刷新:Project右键菜单 > Maven > Update Project...,对于Maven项目来说,单单F5在文件系统层面刷新同步是不够的。
Maven项目编译:
%MAVEN_HOME%\bin
)之后,便可在命令行窗口中、进入到Maven项目的根目录(pom.xml所在地)、然后执行Maven的系列命令了,如:基于Maven的Tomcat Web项目的断点调试也会有一番周折
解决方法:
经历过之前的准备,便可以导入这1.5个人月调研开发的成果项目了,上图,
图5:测试入口界面(Web客户端)
如果说activiti-explorer和kft-activiti-demo都是界面友好、前端与后台并重的话,这次项目则纯粹集中在后台(封装Activiti的引擎功能),因为前端界面(包括表单编辑器、邮件末班编辑器)将由.Net页面(一个企业内部门户网站)完成,衔接关系可参见图2(生命周期)。
项目的完成可划分为6个步骤:
Step1. 在官方范例activiti-explorer中嵌入activiti-rest
前文推荐了导入官方范例项目activiti-explorer作为入门(一分钟入门+导入已有项目),在此第一步,我们就是在其基础上、加上对REST API的支持,也就是嵌入另一个官方范例activiti-rest。
对于缺少J2EE/Spring项目经验的人可能会稍有障碍,其实两个官方范例项目合并的主要破解点如下:
src\main\webapp\WEB-INF\web.xml
org.activiti.rest.common.servlet.ActivitiServletContextListener
,这是一个位于activiti-common-rest依赖包中的类、只用来启动和回收一个ProcessEngine类的实例。
结论:我们无需合并该Listener,因为activiti-explorer已经采用Spring的Bean方式来加载ProcessEngine实例。
src\main\webapp\WEB-INF\web.xml
org.restlet.ext.servlet.ServerServlet
。我们将来扩展REST API将采用Spring MVC方式(后续),但对已有的REST API(5.16.3为止)、我们只能保留现有的Restlet方式入口,不过需注意,activiti-explorer范例与activiti-rest范例所用的org.restlet.application
(Restlet方式下的入口类)并不相同,前者用的是一个只做了简单Router绑定的自定义类org.activiti.rest.explorer.application.ExplorerRestApplication
、仅供modeler和diagram使用,后者(activiti-rest)用的则是包含较严格认证机制的org.activiti.rest.service.application.ActivitiRestServicesApplication
,也因此如官方文档所述、标准REST API的调用都是需要经过Basic认证的。
结论:合并两个官方范例的
org.restlet.application
类,即使得activiti-explorer中的Restlet Application类、继承org.activiti.rest.service.application.ActivitiRestServicesApplication
,覆盖其createInboundRoot()
方法、补充入activiti-rest所需的RestServicesInit.attachResources(router);
。
Step2. 再加入kft-activiti-demo的配置
如从“资料”中文章可以看到,咖啡兔的范例,贡献了很多国内项目级别的成熟经验,比如:
这一步,我们所需做的就是把咖啡兔Demo中可借鉴的配置部分嵌入自己的项目中:
activiti-root
的Maven配置的,而我们自己的项目、应该并不会希望建立这种继承,这种情况下,参照kft-activiti-demo的POM便是个safe的选择。
springServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/spring-mvc.xml
1
springServlet
/
其中spring-mvc.xml
将负责具体的Spring MVC配置(后述)。applicationContext.xml
,咖啡兔Demo是直接把Activiti引擎所需要的配置写在其中的,activiti-explorer范例则是使用
把后台、前端的配置分在两个不同的文件中(activiti-standalone-context.xml
和activiti-ui-context.xml
)。作为我们自己的项目,只需合并在一个activiti-context.xml
中即可。
中的base-package
改成自己的项目包就可以;定制扩展的REST API将以一个个Spring MVC Controller的形式实现,这里的配置将实现对项目包下带有@Controller
标签的类的自动扫描、实例化。Step3. REST API学习
RESTful服务的好处,如前所述、主要是能够使得业务系统所依赖的技术与工作流引擎所依赖的技术(Java)之间解耦。当Activiti引擎作为一个RESTful接口的Web服务运行时(输入输出可以都是JSON数据),浏览器、桌面、移动客户端、基于Java、.Net、Python都可与之交互。
{servlet_mapping}
,在web.xml中已配置)等就形成完整的HTTP请求路径: GET http://{host:port}/{app_name}/{servlet_mapping}/repository/models
配置时则只需{servlet_mapping}
之后的部分,例如 GET repository/models //表示获取“一览”
GET repository/models/{modelId} //表示获取“某个个体”
POST repository/models //表示“新建”一个个体
PUT repository/models/{modelId} //表示“修改”某个个体
src\main\java\org\activiti\rest\service\application\RestServicesInit.java
中: router.attach("/repository/models", ModelCollectionResource.class);
router.attach("/repository/models/{modelId}", ModelResource.class);
......
进一步打开ModelCollectionResource
类,可见Restlet方式的注释(Annotation)声明: @Get("json")
public DataResponse getModels() {......}
@Post
public ModelResponse createModel(ModelRequest request) {......}
Restlet本身我们将不再沿用,但从映射所关联到的各个方法体中我们可以查找到各个功能的幕后实现。Step4. REST API扩展
对于标准REST API无法满足的需求,就需要自行扩展来实现了。扩展会涉及到将来Activiti引擎升级时的兼容问题,不过实际开发中发现、REST接口层的代码量相对来说是很薄的、即便其所用到的表层API发生了变化修改量也不会很大。
{servlet-mapping}
部分是与标准API有所区别的(比如一个是service
另一个就是service-ext
);其他URL风格方面则尽量与官方REST API保持一致。@RequestMapping(...)
)。@Controller
@RequestMapping(value = "/service-ext/repository/models")
public class ModelController {
......
@RequestMapping(value = "deploy/{modelId}")
public String deploy(@PathVariable("modelId") String modelId,
@RequestParam(value="starterUser", required=false) String starterUserId,
@RequestParam(value="starterGroup", required=false) String starterGroupId,
RedirectAttributes redirectAttributes)
{......}
}
可以看到,deploy()方法所对应的URL模式是/service-ext/repository/models/deploy/{modelId}
,其中{modelId}
部分将被直接解析成方法参数modelId
,另外有两个方法参数starterUserId
、starterUserGroup
则对应的是URL中的QueryString部分(且是可选的),一个有效的HTTP请求可能是: GET http://localhost:8080/app/service-ext/repository/models/deploy/1111?starterUser=kermit
更完整的Spring MVC语法文档可参考这个链接,包括@ResponseBody
的使用(方便的将对象返回值转成JSON等格式)、produces="application/json"
、consumes="application/json"
等等。org.activiti.engine.impl.RepositoryServiceImpl
等类是关键类,org.activiti.engine.impl.interceptor.CommandExecutor
则是引擎采用的“Command设计模式”的执行者,各种工作流操作都会被归结为相应的某种Command
、在设置好特定的操作上下文CommandContext
后、就由CommandExecutor
负责执行。一般我们并不需要修改到Command设计模式的领域,而只需参考activiti-rest包中是如何调用其他包的代码就够。Step5. 测试界面(客户端)
如上,我们在后台每定制扩展一个REST API(e.g.“流程设计模型部署”),相应都需要在前端(Web)测试页面(如图5)中增加一组测试。
注意:测试页面其实相当于是“业务系统”的角色,是可以与后台(工作流引擎)分隔为两个WebApp项目的。
测试界面并未使用jquery-ui、blueprint等前端技术,只是简单的JSP(其实纯HTML/JS都完全可以,因为只需Ajax通信+JSON包装/解析)。给出一段调用“流程设计模型部署”API的HTML链接:
流程设计模型部署
再给出一段部署后、已进入工作流运行时阶段(runtime)时、模拟用户提交某份表单完成某个用户任务的JS代码:
图6:运行时阶段的工作流状态查看
参考:
https://www.cnblogs.com/zhao1949/p/5976296.html