• Springboot 整合Activiti流程设计器 完成一个简单的请假流程



    1.前言

    完成这个功能之前,我负责公司的流程管理模板,在众多的解决方案当中,我选择了Activiti 原因有:

    1. 开源、免费
    2. 整合Springboot较容易

    Springboot已经成为开发后端不可缺少的一部分,它是如此完美,如此优秀!

    2.准备

    1.官方文档:https://www.activiti.org/userguide/

    2.下载一份官方准备的demo:https://www.activiti.org/get-started 选择5.X download

    这里选择旧版本的原因是:新的版本会如果突然出现一些不知名的错误,或者是版本问题,会让你头皮发麻不知道怎么解决,网上新版本这一类的错误博客也很少,很浪费时间!

    3.下载解压

    打开wars文件夹,复制war文件包到tomcat,利用tomcat对war包进行一个解压,我们也可以不着急,可以登录官方的demo进行尝试一把

     放置到tomcat启动后产生的文件夹

    地址:htpp://localhost:你的端口号/activiti-explorer

     

    用户名:kermit 密码:kermit

    4.开始整合 mysql + activiti + thymeleaf

    复制代码
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>5.22.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
    复制代码

    按照官网文档的指示,我们引入springboot-starter 依赖 就是这么方便

    2.配置文件

    复制代码
    spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    ##每次应用启动不检查Activiti数据表是否存在及版本号是否匹配 第一次设置为true 会自动创建表结构,之后即可设置为false 提高运行速度
    spring.activiti.database-schema-update=true
    复制代码

    3.复制文件

    复制我们刚才放到tomcat解压后的文件,选择以下文件夹和文件复制到springboot static文件夹下

    •  diagram-viewer 文件夹
    •  editor-app 文件夹
    •  modeler.html

     

    4.加入控制器

    复制这三个文件到任意包下,并且为这三个文件加入注解:@RequestMapping("/service")

    5.修改配置文件

    修改editor-app/app-cfg.js文件,修改其请求路径 和刚才修改的请求路径保持一致

     

    6.剔除启动类里面的安全校验

    @SpringBootApplication(exclude = {
    org.activiti.spring.boot.SecurityAutoConfiguration.class, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class
    })

    7.项目启动完毕,查看数据库

    25张表结构创建完毕,我简单引用一下别人的博客内容如下:

    u  ACT_GE_* : “GE”代表“General”(通用),用在各种情况下;

    u  ACT_HI_* : “HI”代表“History”(历史),这些表中保存的都是历史数据,比如执行过的流程实例、变量、任务,等等。Activit默认提供了4种历史级别:

    Ø  none: 不保存任何历史记录,可以提高系统性能;

    Ø  activity:保存所有的流程实例、任务、活动信息;

    Ø  audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;

    Ø  full:最完整的历史记录,除了包含audit级别的信息之外还能保存详细,例如:流程变量。

    这位大神的链接地址:https://www.cnblogs.com/telwanggs/p/7491564.html 查看更详细的表结构介绍 

    8.访问静态资源被拦截问题--》加入资源处理器

    复制代码
    @Configuration
    @EnableWebMvc
    public class StaticResourceConfig implements WebMvcConfigurer {
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        }
    
    }
    复制代码

    开始流程代码以及逻辑

    1.首先是创建模型

    创建模型呢可谓是流程管理的第一项,创建一个模型,我们呢需要在这个模型上进行流程图的设计与修改,首先把模型创建好

    这里只展示部分代码,展示主要的功能,其他的具体内容,在文章尾部clone项目后进行查看

    @RequestMapping("createModel")
    public String createModel(HttpServletRequest request, HttpServletResponse response){

    String name = "请假流程";
    String description = "这是一个请假流程";

    String id = null;
    try {
    Model model = repositoryService.newModel();
    String key = name;
    //版本号
    String revision = "1";
    ObjectNode modelNode = objectMapper.createObjectNode();
    modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
    modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
    modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);

    model.setName(name);
    model.setKey(key);
    //模型分类 结合自己的业务逻辑
    //model.setCategory(category);

    model.setMetaInfo(modelNode.toString());

    repositoryService.saveModel(model);
    id = model.getId();

    //完善ModelEditorSource
    ObjectNode editorNode = objectMapper.createObjectNode();
    editorNode.put("id", "canvas");
    editorNode.put("resourceId", "canvas");
    ObjectNode stencilSetNode = objectMapper.createObjectNode();
    stencilSetNode.put("namespace",
    "http://b3mn.org/stencilset/bpmn2.0#");
    editorNode.put("stencilset", stencilSetNode);
    repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8"));

    response.sendRedirect(request.getContextPath() + "/static/modeler.html?modelId=" + id);
    } catch (Exception e) {
    e.printStackTrace();
    }

    return "index";
    }

    PS 模型建好了肯定要进行画模型 我对流程设计器进行了汉化,这样操作起来方便多了 我简单画一个

    一个最简单的请假流程 开始---》人事部审批----》总经理审批------》结束

    这张图表示在这个流程开始时候需要填写的一些表单属性 这些都是可以动态加载的!

    任务派遣:就表示的意思是:这个任务到底是指派给谁去执行? 我这里指派给张三去执行 当有人提交这个流程的时候 提交的内容填写完 就会有一个信息到张三这儿!

    2.模型创建完毕,需要把模型发布为可用的流程定义(Process definition) 需要进行部署

    @RequestMapping("deploymentModel")
    @ResponseBody
    public JSONObject deploymentModel(String id) throws Exception {

    //获取模型
    Model modelData = repositoryService.getModel(id);
    byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());

    if (bytes == null) {
    return JsonUtil.getFailJson("模型数据为空,请先设计流程并成功保存,再进行发布。");
    }
    JsonNode modelNode = modelNode = new ObjectMapper().readTree(bytes);

    BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
    if (model.getProcesses().size() == 0) {
    return JsonUtil.getFailJson("数据模型不符要求,请至少设计一条主线流程。");
    }
    byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);

    //发布流程
    String processName = modelData.getName() + ".bpmn20.xml";
    Deployment deployment = repositoryService.createDeployment()
    .name(modelData.getName())
    .addString(processName, new String(bpmnBytes, "UTF-8"))
    .deploy();
    modelData.setDeploymentId(deployment.getId());
    repositoryService.saveModel(modelData);
    return JsonUtil.getSuccessJson("流程发布成功");
    }

    复制代码
     1 @RequestMapping("deploymentModel")
     2     @ResponseBody
     3     public JSONObject deploymentModel(String id) throws Exception {
     4 
     5         //获取模型
     6         Model modelData = repositoryService.getModel(id);
     7         byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
     8 
     9         if (bytes == null) {
    10             return JsonUtil.getFailJson("模型数据为空,请先设计流程并成功保存,再进行发布。");
    11         }
    12         JsonNode modelNode = modelNode = new ObjectMapper().readTree(bytes);
    13 
    14         BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
    15         if (model.getProcesses().size() == 0) {
    16             return JsonUtil.getFailJson("数据模型不符要求,请至少设计一条主线流程。");
    17         }
    18         byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
    19 
    20         //发布流程
    21         String processName = modelData.getName() + ".bpmn20.xml";
    22         Deployment deployment = repositoryService.createDeployment()
    23                 .name(modelData.getName())
    24                 .addString(processName, new String(bpmnBytes, "UTF-8"))
    25                 .deploy();
    26         modelData.setDeploymentId(deployment.getId());
    27         repositoryService.saveModel(modelData);
    28         return JsonUtil.getSuccessJson("流程发布成功");
    29     }
    复制代码

    部署后的流程定义会显示部署ID  这个时候 模板就变成了流程定义

     

    记住这个单词 后面用到的地方特别多!!!!

    这里给解释一下这两个词的意思

    • 流程定义:可以理解为画好的流程,已经发布过的,可以拿来用的一个模板
    • 流程实例:流程实例是流程定义由某个用户启动后,它存在于系统当中,它是一个实例; 

    3.发布后的模型称之为流程定义,开启流程操作 

    开启流程的时候 我们需要一个页面展示这个流程需要填写的字段等 然后提交进行流程的下一步操作

     启动一个流程,这里加载的是我们在画流程图时候输入的表单,这里的提交人就是当前登录的用户(我这里没有整合安全框架 随便写了一个做测试!)

    审核人界面进行审批/驳回(暂时未有这个功能)

     这里是派遣给张三的任务,刚才提交的任务现在到张三的下面 进行一个审核,然后把这个任务提交到下一级(总经理审核)

     这里需要张三进行流程的审批。我们填写表单后,进行提交,现在打开总经理(李四)的任务列表,发现有一个需要待处理的任务!

     

     这里我们既可以看到人事部审核的意见,也可以看到流程开始时候用户提交的内容。流程图也做了相应的更新。

    写在最后

    作为一个刚接触流程的小白,第一次打开这个流程啥都不懂,也慢慢摸索一些,从网上关于流程Activiti的资料很少。

    总的来说就是:

    • 杂乱,没有一个较好的学习资料可以参考
    • 这一方面的博客/文章较少

    推荐学习:http://www.agilebpm.cn/

    这是博主最近才接触到的一个国内的流程开源版本,总体来说比国外这款Activiti好用,简洁 Api较多,简易

    也不能说Activiti人家不好,就是说白了有点复杂,小白一下子上手不了,推荐从这款开源版本开始学习

    参考:

    https://www.cnblogs.com/zhouyun-yx/p/10410274.html

    https://blog.csdn.net/sdufexsh/article/details/83111965#%E8%8E%B7%E5%8F%96UserTask%E4%B8%8A%E5%AE%9A%E4%B9%89%E7%9A%84%E5%8A%A8%E6%80%81%E8%A1%A8%E5%8D%95

    https://www.jianshu.com/p/701056e672a4

    https://www.cnblogs.com/telwanggs/p/7491564.html

    错误解决:

    class path resource [processes/] cannot be resolved to URL because it does not exist

    解决方案:创建这个processes文件夹到resources下 随便丢个文件进去

    java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

    解决方案:剔除启动校验问题,向上看第六点

    Could not write PNG file because no WriteAdapter is availble

    解决方案:

    依赖文件引入缺少问题 引入一下即可

    <dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-codec</artifactId>
    <version>1.7</version>
    </dependency>
    <dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-css</artifactId>
    <version>1.7</version>
    </dependency>
    <dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-svg-dom</artifactId>
    <version>1.7</version>
    </dependency>
    <dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-svggen</artifactId>
    <version>1.7</version>
    </dependency>

    代码实例开源:

    码云:https://gitee.com/mrc1999/activiti

    原文链接:https://www.cnblogs.com/ChromeT/p/11261101.html

  • 相关阅读:
    java中常量定义在interface中好还是定义在class中
    CharacterEncodingFilter-Spring字符编码过滤器
    Integer判断相等,到底该用==还是equals
    ThreadLocal实现session中用户信息 的线程间共享
    分布式部署引发的问题
    分布式部署
    LogBack通过MDC实现日志记录区分用户Session
    Fragment 简介 基础知识 总结 MD
    直播 相关技术文章 相关调研文章
    直播 背景 技术体系 乐视云直播Demo
  • 原文地址:https://www.cnblogs.com/cyl048/p/11911293.html
Copyright © 2020-2023  润新知