Flowable工作流
几年没做工作流了,时过进迁,时不时忆起当年,还时那般激情热血。牢骚完,感慨闭,几年后的今天又要开始做自定义工作流的开发,今天首先分享一下我的第一步,整合Web流程设计器官方demo自带的modeler。这里要说几句题外话,本身原计划还时打算继续使用activiti的,想当年我还是基于5.13实现过自定义工作流,现在几年过去了怎么也该6.0了吧,结果不了解不知道一了解吓一跳,activiti核心成员全部出走以Activiti5.22为基础另外创立了Flowable,并且已经发布了6.0 releace,而Activiti这边的6.0依然还在beta版本。了解到这里自不用多讲,肯定选择Flowable咯。
当年我在5.13的时候整合modeler的时候,那可是相当的简单啊,整个过程也就几十分钟的事儿,从官方demo里扣出editor文件夹,然后给自己的系统添加rest框架支持同时添加modeler需要的几个rest路由就搞定。但是这次整合Flowable的modeler着实把我恶心了一把,首先官方demo就搞得无比复杂,使用了太多前沿技术,例如Spring security、angularJs、freemarker等,另外正因为大量使用freemarker导致页面极度碎片化,对于新人研究demo代码带了极大的困难。当然,这些还难不倒我。
观察官方demo,发现现在的demo分成了多个项目,其中关键的就是flowable-idm、flowable-modeler,实际使用发现idm主要负责人员和权限管理,modeler里包含的流程模型管理、app管理和form管理,其中app管理是一个新的概念,实际意义其实就是对流程模型进行了一个分类,和流程模型属于一对多的关系。
大致关系明白之后,开始阅读源码,过程有多坑爹就不多说了,没注释,页面极度碎片化,反正挺痛苦,最后总结是modeler项目可独立使用,可配置jdbc访问指定数据库,但是因为使用了Spring security权限框架做了统一的权限管理和单点登录,故而想要不做修改直接放入自己项目则必须同时使用idm和modeler两个项目,而且idm里面使用的还是ACT_ID_*系列表,无法使用自己系统的用户体系。那么我们可以得出一个结论,要么修改idm让其使用我们自己的用户体系,要么修改modeler取消使用Spring security,不依赖idm,从而使用我们的用户体系。权衡利弊后,我选择了后者。
确定方案后,提取modeler的源码,使用IDE导入maven项目,开始寻找修改的方案。
首先找到web.xml
,发现如下配置。
1 2 3 |
|
进入这个类后,又发现如下代码。
1 2 3 4 5 6 7 8 9 |
/** * Initializes Spring Security. */ private void initSpringSecurity(ServletContext servletContext, EnumSet |
毫无疑问,就是通过这里添加了Spring Security的拦截器,拦截所有请求,进行的登录验证,遂将其注释之,重新运行测试,果然跳过登录验证。但是又发现顶部导航条没有显示出来,打开控制发现:
1 2 3 4 |
Request URL:http://localhost:8080/flowable-modeler/app/rest/account Request Method:GET Status Code:404 Not Found Remote Address:[::1]:8080 |
翻找源码后发现,是app.js里293行发出的这个请求。
1 2 3 4 5 6 |
$http.get(FLOWABLE.CONFIG.contextRoot + '/app/rest/account') .success(function (data, status, headers, config) { $rootScope.account = data; $rootScope.invalidCredentials = false; $rootScope.authenticated = true; }); |
追踪到java里发现这个请求实际上就是找Spring Security获取当前登录者的信息。好嘛,那我们继续注视掉这个请求,直接把success里面的代码复制出来,给$rootScope.account
写死一个json。
1 2 3 |
$rootScope.account = {id:1, firstName:"xxx", lastName:"xxx", email:"xxx", fullName:"xxx"}; $rootScope.invalidCredentials = false; $rootScope.authenticated = true; |
再测试发现一切正常了,不过右上角显示的当前登录人的名称变成了xxx。好嘛,原来上面这个请求获取用户的意义就在这里。那好办了,改写上面的请求,把url换成我们的,把我们的用户重新封装成Modeler里的User类型,再返回即可搞定。
继续往下测试,创建个模型试试。呵呵,空指针。。。查看后发现是ModelService
里的createModel方法报出的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Override public Model createModel(ModelRepresentation model, String editorJson, User createdBy) { Model newModel = new Model(); newModel.setVersion(1); newModel.setName(model.getName()); newModel.setKey(model.getKey()); newModel.setModelType(model.getModelType()); newModel.setCreated(Calendar.getInstance().getTime()); newModel.setCreatedBy(createdBy.getId()); newModel.setDescription(model.getDescription()); newModel.setModelEditorJson(editorJson); newModel.setLastUpdated(Calendar.getInstance().getTime()); newModel.setLastUpdatedBy(createdBy.getId()); persistModel(newModel); return newModel; } |
调试发现传入参数createdBy
拿到的是个null,继续往上查找:
1 2 3 4 5 6 7 8 |
protected ModelRepresentation createNewModel(String name, String description, Integer modelType, String editorJson) { ModelRepresentation model = new ModelRepresentation(); model.setName(name); model.setDescription(description); model.setModelType(modelType); Model newModel = modelService.createModel(model, editorJson, SecurityUtils.getCurrentUserObject()); return new ModelRepresentation(newModel); } |
发现createBy
来源于SecurityUtils.getCurrentUserObject()
,好嘛,我们都绕过了登录验证没有登录,这里当然是空了,而且我们并不打算使用Spring security,那么我们就需要从我们自己的单点登录中获取用户即可,为了我们能够继续往下验证先手动new一个User放进去,再次测试,发现成功。继续测试后续其他功能,可以发现全部OK。
当测试到流程图复制功能时,出现了问题,复制成功后,modeler上看不到复制出来的流程图,但是数据库ACT_DE_MODEL
表中却实实在在多了一条数据,为什么显示不出来呢,对比发现复制出来的新数据中model_type字段为null,正常的则为0,手动修改后刷新页面,果然就正常了,难道是官方的一个BUG?没有继续深究了,先解决它,找到请求发送的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$scope.ok = function () { if (!$scope.model.process.name || $scope.model.process.name.length == 0 || !$scope.model.process.key || $scope.model.process.key.length == 0) { return; } $scope.model.loading = true; $http({method: 'POST', url: FLOWABLE.CONFIG.contextRoot + '/app/rest/models/'+$scope.model.process.id+'/clone', data: $scope.model.process}). success(function(data) { $scope.$hide(); $scope.model.loading = false; $rootScope.editorHistory = []; $location.path("/editor/" + data.id); }). error(function() { $scope.model.loading = false; $modal.$hide(); }); }; |
ajax提交的数据部分写的是data: $scope.model.process
,那么在$scope.model.loading = true;
后追加一句$scope.model.process.modelType = 0;
强制把modelType设置为0。重新测试发现成功。至于$scope.model.process.modelType
为什么会是null,到底是不是BUG,有兴趣的同学可自行往下研究。
开发接入自己的Session同步或者单点登录,加上自己的登录拦截,即可打工搞成。
首先在ApplicationConfiguration
中配置拦截器的bean,因为modeler没有使用xml的配置方式,所以要在ApplicationConfiguration
类中加入java代码来配置bean
1 2 3 4 |
@Bean public static SessionFilter sessionFilter(){ return new SessionFilter(); } |
SessionFilter
就是一个普通的实现javax.servlet.Filter
接口的拦截器java类
然后在WebConfigurer
中新增一个自己的初始化方法,写入以下代码:
1 2 |
FilterRegistration.Dynamic sessionFilter = servletContext.addFilter("sessionFilter", new DelegatingFilterProxy()); sessionFilter.addMappingForUrlPatterns(disps, false, "/*"); |
这里为什么使用new DelegatingFilterProxy()
,而不是直接new SessionFilter()
呢?
因为这样写,在SessionFilter
中才能够使用Spring的上下文环境。而直接new SessionFilter()
,则没有被Spring管理,则无法使用Spring的上下文环境,不能使用Spring的上下文环境最直接的影响自然就是不能注入bean,那么你的service在里面就无法使用。
第三步中提到了所有的SecurityUtils.getCurrentUserObject()
都要替换成我们的单点登录获取用户的代码。如果需要用到session的话,直接使用
1 |
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession(); |
这句代码是无法获取到session的,直接空指针异常,原因是Modeler本身并没有加入RequestContextListener
这个listener,解决办法自然是我们帮它加入。为了和整个modeler代码保持统一的编码风格,我们在web.xml
中加入自然不好,于是我们还是找到WebConfigurer
类,在initSpring方法最后加入一句代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * Initializes Spring and Spring MVC. */ private void initSpring(ServletContext servletContext, AnnotationConfigWebApplicationContext rootContext) { log.debug("Configuring Spring Web application context"); AnnotationConfigWebApplicationContext appDispatcherServletConfiguration = new AnnotationConfigWebApplicationContext(); appDispatcherServletConfiguration.setParent(rootContext); appDispatcherServletConfiguration.register(AppDispatcherServletConfiguration.class); log.debug("Registering Spring MVC Servlet"); ServletRegistration.Dynamic appDispatcherServlet = servletContext.addServlet("appDispatcher", new DispatcherServlet(appDispatcherServletConfiguration)); appDispatcherServlet.addMapping("/app/*"); appDispatcherServlet.setLoadOnStartup(1); servletContext.addListener(RequestContextListener.class); } |
自此,modeler整合全部完成。Thanks for your reading。
原文
https://veevv.com/2017/03/17/flowable-modeler-integrate/#
本文标题:如何整合Flowable-modeler到自己的项目中
文章作者:Victor Jones
发布时间:2017-03-17, 00:00:00
最后更新:2019-04-14, 07:02:08
原始链接:https://veevv.com/2017/03/17/flowable-modeler-integrate/
许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。
.com/2017/03/17/flowable-modeler-integrate/#