Camunda 工作流引擎集成

camunda 集成

1.流程引擎集成

由于我的项目不使用spring相关框架, 但是内部也有实例生命周期管理的框架, 集成思路都差不多

首先通过 ProcessEngineConfiguration 来配置流程引擎并且启动,

ProcessEngineConfiguration 分为几种

1,JtaProcessEngineConfiguration 支持流程事务与框架事务整合的配置(当前方案采用)

2.StandaloneProcessEngineConfiguration 标准配置, 可能有些功能会缺失

3.和Spring 可以整的的 ProcessEngineConfiguration, 使用了Sping 框架可以优先选择

ProcessEngineConfiguration 主要的配置项是

1. configuration.setIdGenerator(idGenerator) 
使用 UUID 生成主键

2.configuration.setDataSource(dataSource)
设置数据源(用于保存流程的表结构和数据),当然如果有多数据源,最好jta事务管理多数据源事务
如果流程中调用系统服务时出错保证事务回滚.

3.configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
设置流程引擎数据库生成表策略,我使用的是每次启动自动更新表结构,生产环境初始化一次就可关闭此选项了

4.configuration.setCustomFormTypes(formFieldTypes)
设置流程引擎自定义表单类型,类型需要实现 FormType 接口,后续会说一下如何完成添加自定义表单类型,camunda 有个依赖 camunda-common-typed-values jar包内有一些表单类型可用,但是业务系统所需要的表单类型远不止这些,需要更多的自定义类型支持工作流引擎

5.configuration.setCustomPreVariableSerializers(typeValueSerializers)
设置自定义字段解析器, 流程自定义表单类型需要配合类型解析器来工作,解析器内定义如果将你的自定义类型保存到数据表 variables ,和如果从数据表取出来变成自定义类型.

6.configuration.setClassLoader(classLoader)
设置类加载器, 如果需要使用scriptTask groovy 脚本等,需要使用当前类加载器

7.configuration.setTransactionManager(getTransactionManager())
设置事务管理器,springboot 的configuration 好像有个属性直接集成事务管理器了

8.configuration.setEnableFetchScriptEngineFromProcessApplication(true)
设置脚本执行上下文,主要是使用 serviceTask 或者scriptTask 时有用

9.processEngine = configuration.buildProcessEngine()
启动工作流,并且初始化流程引擎,流程引擎是camunda 各种service 的入口了, 熟悉activitie 的这里应该很熟,基本activiti系列的工作流都是这么搞.

有了流程引擎就可以使用camunda 工作流了, 具体各种services 我不多做赘述, 官方文档写的不是很全面,建议还是下载个源码看,来的快点.

camunda 提供了离线的流程编辑器 ,camunda modeler,流程部署方式是线下设计好,再来部署,其实也可以自己集成一个线上设计器.

具体过程为:

1.jquery集成
bpmn.io 为BPMN2.0 提供了很强大的图形化管理工具,但是文档没有那么全面,我这里弄了一个比较省事比较快速的集成方式,还有react vue 上集成方式.后面说

bpmn.io 示例项目地址 到这里下载示例项目   properties-panel , 这个项目是带属性面板的, 同时也有camunda 的模块内容,

下载的项目就是一个Grunt 前端项目 使用命令  npm run dev 就可以运行, 运行后编译的前端代码在dist 中,

可以在这个基础上简单修改一下,让该项目支持jquery

if (jQuery){
    jQuery.fn.extend({
        bpmnModeler:function (config = {}) {....这里写一下如何初始化流程设计器})

    })

}
记得需要依赖
// 流程模型设计器
import BpmnModeler from "bpmn-js/lib/Modeler";

// 设计器属性面板
import propertiesPanelModule from "bpmn-js-properties-panel";

// 属性面板支持camunda 的module
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";

// camunda 的Moddle 的配置
import camundaModdleDescriptorfrom "camunda-bpmn-moddle/resources/camunda.json";

// 一个空的流程文件,用于新建流程引用
import diagramXMLfrom "../resources/newDiagram.bpmn";

// 国际化文件,这个得自己来写
import ZhCN from "../resources/ZhCN";

具体代码可以参考当前的前端项目,可以使用grunt 生成的代码作为一个组件,直接引用就可以调用$('XXX').bpmnModeler({....属性})
来初始化一个流程设计器

2.react 集成
react 集成还算比较简单,需要引用的依赖为

"bpmn-js":"^7.4.0",

"bpmn-js-properties-panel":"^0.37.5",

"camunda-bpmn-moddle":"^4.4.1",

"diagram-js":"^6.8.0",

主要就是这几个,当前你也可以添加自定义的流程模块,这里就不讲了,

import {FullScreen,useFullScreenHandle }from 'react-full-screen';

// @ts-ignore

import Modeler from 'bpmn-js/lib/Modeler';

// @ts-ignore

import PropertiesPanelModulefrom 'bpmn-js-properties-panel';

// @ts-ignore

import PropertiesProviderModulefrom 'bpmn-js-properties-panel/lib/provider/camunda';

// @ts-ignore

import CamundaModdlefrom 'camunda-bpmn-moddle/resources/camunda.json';

在react 中弄个组件,应用这些模块

useEffect(() => {

modeler.current =new Modeler({

container:'#modeler-main',

propertiesPanel: {

parent:'#modeler-panel',

},

additionalModules: [

PropertiesPanelModule,

PropertiesProviderModule,

{

translate: ['value',customTranslate],

},

],

moddleExtensions: {

camunda: CamundaModdle,

},

});

handleDiagram();

}, []);

初始化modeler 就可以生成一个流程设计器了


流程设计器提供了几个接口可以帮助完成很多操作
1.缩小流程图

bpmnModeler.get("zoomScroll").zoom(-1, {

x:container.get(0).offsetWidth,

y:container.get(0).offsetHeight,

});

2.放大流程图

zoomOut.click(function (e) {

e.stopPropagation();

e.preventDefault();

bpmnModeler.get("zoomScroll").zoom(1, {

x:container.get(0).offsetWidth,

y:container.get(0).offsetHeight,

});

});

3.快捷键
初始化流程时配置属性
keyboard: {

bindTo:window,

},

完整的jquery 使用流程设计器代码

import {debounce }from "min-dash";

import BpmnModeler from "bpmn-js/lib/Modeler";

import propertiesPanelModule from "bpmn-js-properties-panel";

import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";

import camundaModdleDescriptorfrom "camunda-bpmn-moddle/resources/camunda.json";

import diagramXMLfrom "../resources/newDiagram.bpmn";

import ZhCN from "../resources/ZhCN";

// { save:'#saveBtn',reset:'#resetBtn',zoomIn:'#zoomInBtn',zoomOut:'#zoomOutBtn',downloadDiagram:'downloadDiagramBtn',downloadSvg:'downloadSvgBtn'}

if (jQuery) {

jQuery.fn.extend({

bpmnModeler:function (config = {}) {

// 初始化元素

      const container =jQuery(this);

// 添加元素

      const canvas =jQuery('

');

const panel =jQuery('

');

const messageInfo =jQuery(

'

放入文件加载工作流 或 新建工作流 开始工作流设计
'

      );

const messageError =jQuery(

'

无法显示 BPMN 2.0 流程图.

错误详细信息

'

      );

const url =container.get(0).dataset.url;

const form =container.get(0).dataset.form;

const method =container.get(0).dataset.method;

const resourceId =container.get(0).dataset.resourceId;

const deploymentId =container.get(0).dataset.deploymentId;

const sessionToken =container.get(0).dataset.sessionToken;

const initUrl =container.get(0).dataset.initUrl;

const backUrl =container.get(0).dataset.backUrl;

const save = config.save ?jQuery(config.save) :undefined;

const reset = config.reset ?jQuery(config.reset) :undefined;

const zoomIn = config.zoomIn ?jQuery(config.zoomIn) :undefined;

const zoomOut = config.zoomOut ?jQuery(config.zoomOut) :undefined;

const downloadDiagram = config.downloadDiagram

        ?jQuery(config.downloadDiagram)

:undefined;

const downloadSvg = config.downloadSvg

        ?jQuery(config.downloadSvg)

:undefined;

container.append(canvas);

container.append(panel);

container.append(messageInfo);

container.append(messageError);

// 初始化编辑器

      const bpmnModeler =new BpmnModeler({

container:canvas,

keyboard: {

bindTo:window,

},

propertiesPanel: {

parent:panel,

},

additionalModules: [

propertiesPanelModule,

propertiesProviderModule,

{

translate: ["value",ZhCN],

},

],

moddleExtensions: {

camunda: camundaModdleDescriptor,

},

});

container.addClass("bpmn-content");

container.removeClass("with-diagram");

if (save)save.addClass("disabled");

if (reset)reset.addClass("disabled");

if (zoomIn)zoomIn.addClass("disabled");

if (zoomOut)zoomOut.addClass("disabled");

// 含有流程定义ID,加载流程文件

      if (resourceId &&initUrl) {

jQuery(function () {

jQuery.ajax({

url:initUrl,

type:"get",

headers: {

Accept:"application/json; charset=utf-8",

zkitSessionToken:sessionToken,

},

data: {deploymentId,resourceId },

success:function (res) {

openDiagram(res.data);

},

error:function (e) {

container.removeClass("with-diagram").addClass("with-error");

messageError.text("加载工作流模型失败");

},

});

});

}

const exportArtifacts =debounce(async function () {

if (downloadSvg) {

try {

const {svg } =await bpmnModeler.saveSVG();

setEncoded(downloadSvg,"diagram.svg",svg);

}catch (err) {

console.error("Error happened saving SVG: ", err);

setEncoded(downloadSvg,"diagram.svg",null);

}

}

if (downloadDiagram) {

try {

const {xml } =await bpmnModeler.saveXML({format:true });

setEncoded(downloadDiagram,"diagram.bpmn",xml);

}catch (err) {

console.log("保存 XML 错误: ", err);

setEncoded(downloadDiagram,"diagram.bpmn",null);

}

}

},500);

bpmnModeler.on("commandStack.changed",exportArtifacts);

// 处理按钮事件

      jQuery("#bpmn-create-diagram").click(function (e) {

e.stopPropagation();

e.preventDefault();

openDiagram(diagramXML);

});

if (reset) {

reset.click(function (e) {

e.stopPropagation();

e.preventDefault();

const bpmnCanvas =bpmnModeler.get("canvas");

bpmnCanvas.zoom("fit-viewport");

});

}

if (zoomIn) {

zoomIn.click(function (e) {

e.stopPropagation();

e.preventDefault();

bpmnModeler.get("zoomScroll").zoom(-1, {

x:container.get(0).offsetWidth,

y:container.get(0).offsetHeight,

});

});

}

if (zoomOut) {

zoomOut.click(function (e) {

e.stopPropagation();

e.preventDefault();

bpmnModeler.get("zoomScroll").zoom(1, {

x:container.get(0).offsetWidth,

y:container.get(0).offsetHeight,

});

});

}

if (save) {

save.click(function (e) {

e.stopPropagation();

e.preventDefault();

if (form &&url &&method) {

const formData =new FormData(jQuery(form).get(0));

const deploymentName =formData.get("deploymentName");

const source =formData.get("source");

// 表单验证

            bpmnModeler.saveXML({format:true },function (err, xml) {

if (err) {

messageError.text(err);

return;

}

let fileName ="";

if (source) {

fileName =source +".bpmn";

}else if (deploymentName) {

fileName =deploymentName +".bpmn";

}

formData.append(

"file",

new Blob([xml], {type:"text/xml" }),

fileName

              );

jQuery.ajax({

url,

type:method,

data:formData,

cache:false,

headers: {

zkitSessionToken:sessionToken,

},

processData:false,

contentType:false,

success:function (res) {

config.onSave && config.onSave();

if (res && res.deploymentId) {

backUrl &&jQuery(location).prop("href",backUrl);

}else {

alert("决策表部署失败,请检查设计模型");

}

},

});

});

}

});

}

// drop 文件引入

      if (!window.FileList || !window.FileReader) {

window.alert(

"浏览器版本不支持拖动文件加载. " +

"请尝试使用 Chrome, Firefox 或 the Internet Explorer > 10 以上重试."

        );

}else {

registerFileDrop(container, openDiagram);

}

function setEncoded(link, name, data) {

var encodedData =encodeURIComponent(data);

if (data) {

link.removeClass("disabled").attr({

href:"data:application/bpmn20-xml;charset=UTF-8," +encodedData,

download: name,

});

}else {

link.addClass("disabled");

}

}

function registerFileDrop(element, callback) {

function handleFileSelect(e) {

e.stopPropagation();

e.preventDefault();

const files = e.dataTransfer.files;

const file =files[0];

const reader =new FileReader();

reader.onload =function (e) {

const xml = e.target.result;

callback(xml);

};

reader.readAsText(file);

}

function handleDragOver(e) {

e.stopPropagation();

e.preventDefault();

e.dataTransfer.dropEffect ="copy";// Explicitly show this is a copy.

        }

element.get(0).addEventListener("dragover", handleDragOver,false);

element.get(0).addEventListener("drop", handleFileSelect,false);

}

async function openDiagram(xml) {

try {

await bpmnModeler.importXML(xml);

// zoom to fit full viewport

          container.removeClass("with-error").addClass("with-diagram");

const bpmnCanvas =bpmnModeler.get("canvas");

bpmnCanvas.zoom("fit-viewport");

if (save)save.removeClass("disabled");

if (reset)reset.removeClass("disabled");

if (zoomIn)zoomIn.removeClass("disabled");

if (zoomOut)zoomOut.removeClass("disabled");

}catch (err) {

container.removeClass("with-diagram").addClass("with-error");

messageError.text(err.message);

if (save)save.addClass("disabled");

if (reset)reset.addClass("disabled");

if (zoomIn)zoomIn.addClass("disabled");

if (zoomOut)zoomOut.addClass("disabled");

}

}

},

});

}else {

console.log("流程设计器需要加载 jquery");

}

react 流程设计器完整代码

import React, {useEffect,useRef }from 'react';

import {message,Space, Spin }from 'antd';

import {useRequest }from 'umi';

import {makeStyles, Theme,Toolbar,Typography }from '@material-ui/core';

import { Button,useLang }from '@/components';

import {useHotkeys }from 'react-hotkeys-hook';

import {FullScreen,useFullScreenHandle }from 'react-full-screen';

// @ts-ignore

import Modeler from 'bpmn-js/lib/Modeler';

// @ts-ignore

import PropertiesPanelModulefrom 'bpmn-js-properties-panel';

// @ts-ignore

import PropertiesProviderModulefrom 'bpmn-js-properties-panel/lib/provider/camunda';

// @ts-ignore

import CamundaModdlefrom 'camunda-bpmn-moddle/resources/camunda.json';

import customTranslate from './lang.zh';

import 'bpmn-js-properties-panel/styles/properties.less';

import 'bpmn-js/dist/assets/diagram-js.css';

import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';

const blankBpmn =

'\n' +

'\n' +

\n' +

'    \n' +

'  \n' +

\n' +

'    \n' +

'      \n' +

'        \n' +

'      \n' +

'    \n' +

'  \n' +

'';

const useStyles =makeStyles((theme: Theme) => ({

root: {

flexGrow:1,

display:'flex',

flexDirection:'column',

flexWrap:'nowrap',

position:'relative',

background:'#fff',

'& .fullscreen': {

flexGrow:1,

display:'flex',

flexDirection:'column',

flexWrap:'nowrap',

},

},

main: {

flexGrow:1,

'& .bjs-powered-by': {

display:'none',

},

},

toolbar: {

paddingLeft: theme.spacing(2),

paddingRight: theme.spacing(2),

background: theme.palette.background.default,

},

title: {

flexGrow:1,

},

panel: {

position:'absolute',

top:0,

bottom:0,

right:0,

width:'260px',

zIndex:10,

borderLeft:'1px solid #ccc',

overflow:'auto',

'&:empty': {

display:'none',

},

'& > .djs-properties-panel': {

paddingBottom:'70px',

minHeight:'100%',

},

},

}));

interface BpmnModelerProps {

title?: React.ReactNode;

service:any;

params:any;

onDeploy?: (xml:any) =>void;

}

const BpmnModeler: React.FC = ({ title, service, params, onDeploy }) => {

const lang =useLang();

const classes =useStyles();

const modeler =useRef();

const handle =useFullScreenHandle();

const {loading,run } =useRequest(service, {

manual:true,

onSuccess: ({data: res }) => {

if (res.data) {

modeler.current.importXML(res.data, (error:any) => {

if (error) {

message.error(error);

}

});

}else {

modeler.current.importXML(blankBpmn);

}

},

});

// 打开流程设计器

  const handleDiagram =async () => {

if (params.resourceId || params.deploymentId) {

run(params);

}else {

modeler.current.importXML(blankBpmn);

}

};

// 保存流程

  const handleSave = () => {

if (modeler.current) {

modeler.current.saveXML({format:true }, (err:any, xml:any) => {

if (err) {

message.error(err);

return;

}

onDeploy &&onDeploy(xml);

});

}

};

// 重置

  const handleReset = () => {

if (modeler.current) {

const canvas =modeler.current.get('canvas');

canvas?.resized();

canvas?.zoom('fit-viewport','auto');

}

};

// 放大

  const handleZoomIn = () => {

if (modeler.current) {

// eslint-disable-next-line no-underscore-dangle

      const container =modeler.current._container;

modeler.current.get('zoomScroll').zoom(1, {

x:container.offsetWidth,

y:container.offsetHeight,

});

}

};

// 缩小

  const handleZoomOut = () => {

if (modeler.current) {

// eslint-disable-next-line no-underscore-dangle

      const container =modeler.current._container;

modeler.current.get('zoomScroll').zoom(-1, {

x:container.offsetWidth,

y:container.offsetHeight,

});

}

};

const handleUndo = () => {

if (modeler.current) {

modeler.current.get('commandStack').undo();

}

};

const handleRedo = () => {

if (modeler.current) {

modeler.current.get('commandStack').redo();

}

};

useHotkeys('ctrl+z', handleUndo);

useHotkeys('ctrl+shift+z', handleRedo);

useEffect(() => {

modeler.current =new Modeler({

container:'#modeler-main',

propertiesPanel: {

parent:'#modeler-panel',

},

additionalModules: [

PropertiesPanelModule,

PropertiesProviderModule,

{

translate: ['value',customTranslate],

},

],

moddleExtensions: {

camunda: CamundaModdle,

},

});

handleDiagram();

}, []);

return (

{title}

                icon="expand"

                size="small"

                onClick={handle.enter}                tooltip={lang('fullScreen')}              />

你可能感兴趣的:(Camunda 工作流引擎集成)