camunda 应用实践
使用camunda作后端接口编排
我们在使用camnuda作为后端接口编排组件后,在实际开发应用中遇到了不少问题,在此做一些总结,供后期查阅
前端改造
camunda 官方提供了流程图绘制客户端,但是由于我们是B/S系统所以需要将流程设计器集成到网页端,于是我们使用vue3+ts结合bpmn.js做了对camunda属性面板改造,实现如下功能:
- vue3+ts+bpmn.js ,将camunda流程设计器集成到网页端;
- 使用bpmn2.0中的服务任务 + 网关 + 流转条件实现接口编排;
- 使用bpmn2.0中的事物子流程 + 边界异常事件 + 取消事件 + 补偿节点实现分布式事务控制;
后端驱动
使用camunda流程引擎api实现流程初始化,部署,实例启动等:
- 利用代理表达式 ,实现对流程设计器中配置的接口进行触发调用;
- 使用监听器实现流程返回结果处理;
代码实现 & 效果展示
首先前端引入bpmn.js依赖
npm i bpmn-js --save
引入camuda.json标准和国际化文件
编写modeler代码
import './modeler.css';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import 'diagram-js-minimap/assets/diagram-js-minimap.css'
import { defineComponent, onMounted } from 'vue';
import createDefaultBpmnXml from '../../../bpmn/defaultBpmnXml';
import camoundaModdel from '../../../bpmn/resources/camunda.json';
import translate from '../../../bpmn/i18n';
import { BpmnStore } from '../../../bpmn/store';
import minimapModule from 'diagram-js-minimap'
export default defineComponent({
name: 'Modeler',
setup() {
const bpmnContext = BpmnStore;
onMounted(() => {
bpmnContext.initModeler({
container: '#modeler-container',
additionalModules: [
//添加翻译
{ translate: ['value', translate('zh')] },
//小地图
minimapModule
],
moddleExtensions: {
camunda: camoundaModdel,
},
});
const defaultProcessIdAndName = '1';
bpmnContext
.importXML(createDefaultBpmnXml(defaultProcessIdAndName, defaultProcessIdAndName))
.then((result: Array<string>) => {
if (result.length) {
console.warn('importSuccess warnings', result);
}
})
.catch((err: any) => {
console.warn('importFail errors ', err);
});
});
return () => <div id="modeler-container"/>;
},
});
界面效果:
后端代理表达式代码
@Component("codeInterfaceHandler")
public class CodeInterfaceHandler implements JavaDelegate {
@Resource
private RestTemplate restTemplate;
@Override
public void execute(DelegateExecution execution) throws Exception {
// 获取接口id
String interfaceId = getInterfaceId(execution);
// 根据接口id查询接口
ServiceAssert.isNotNull(interfaceId, "未挂接接口id");
String url = PROTOCOL + SysConstants.SERVICE_NAME + GET_INTERFACE_BY_ID_URL + interfaceId;
ApiResult apiResult = restTemplate.getForObject(url, ApiResult.class);
ServiceAssert.isNotNull(apiResult.getData(), apiResult.getMessage());
SysInterface sysInterface = JSONUtil.toBean(JSONUtil.toJsonStr(apiResult.getData()), SysInterface.class);
ServiceAssert.isNotNull(sysInterface, "接口信息不存在");
}
}
后端监听器代码
@Component("codeFlowEndEventListener")
public class CodeFlowEndEventListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) throws Exception {
// 任务结束,组织返回数据
Map result = new HashMap(16);
execution.getVariablesLocal().forEach((k, v) -> {
if (execution.getProcessInstance().getVariable(k) != null) {
result.put(k, execution.getProcessInstance().getVariable(k));
}
});
if (!CollectionUtil.isEmpty(result)) {
execution.getProcessInstance().setVariable(CodeLogicalFlowConstants.RETURN_DATA_OF_FLOW, result);
}
}
}
流程驱动代码:
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> process(String flowCode, String json) {
ServiceAssert.isTrue(StrUtil.isNotBlank(json), "未传入逻辑流程入参");
Map<String, Object> paramMap = JSONUtil.parseObj(json);
ProcessInstanceWithVariables processInstance;
try {
// 流程启动参数,是逻辑流程生成接口入参,将放入流程变量中
processInstance = (ProcessInstanceWithVariables) runtimeService.startProcessInstanceByKey(flowCode, paramMap);
} catch (Exception e) {
log.error("流程启动出错", e);
throw new ServiceException(e.getMessage());
}
// 任务结束,组织返回数据
return getReturnData(processInstance);
}