目录
1.后台参考,spring boot,activiti7(虽然是7,因为用的shiro,没有使用spring security,新特性都没用,用过activiti5,6的话都不用多看,常用的区别不大)
2.前端参考,vue,bpmnjs(用的camunda解析器,和activiti有区别,问题主要集中在这)
效果
前端代码
后台代码
吐槽:
主要参考两位大佬的文章,代码直接拿着跑,然后集成(Ctrl+c+v)进了公司项目
如果需要表单,业务也明确,建议用外部表单,动态表单功能比较受限.
1.后台参考,spring boot,activiti7(虽然是7,因为用的shiro,没有使用spring security,新特性都没用,用过activiti5,6的话都不用多看,常用的区别不大)
2.前端参考,vue,bpmnjs(用的camunda解析器,和activiti有区别,问题主要集中在这)
如果是刚接触工作流,可以看看这两篇,很详细,都是直接上代码
camunda建议看官方文档,直接网页翻译对着看,说真的没找到特别好的文章,如果有好的希望好心人给我也发一份共同学习
一个小问题,Activiti7的M4版本缺失字段Bug,这是Oracle
貌似好多版本都有问题,不知道会不会变成Activiti的特色
alter table ACT_RE_DEPLOYMENT add PROJECT_RELEASE_VERSION_ varchar(255) DEFAULT NULL;alter table ACT_RE_DEPLOYMENT add VERSION_ varchar(255) DEFAULT NULL;
效果
查询任务时可以编辑表单信息,保存表单数据是单独自定义的表,历史任务id和运行任务id一样,所以查一张表就行
前端代码
前端用的bpmnjs,其实搭配camunda比较合适
直接以字符串形式部署,把camunda标签,属性换成对应activiti了
async addByString() {try {const result = await this.bpmnModeler.saveXML({ format: true });const xml = result.xml;//xml转json,用json处理后在转xmlconst $x2js = new x2js();const jsonObj = $x2js.xml2js(xml);this.getFormProperty(jsonObj);let newXml = $x2js.js2xml(jsonObj);act1.addByString({xmlBPMN: newXml,deployName: 'addByString1'});} catch (err) {console.log(err);}},
开始是直接字符串替换,最好不要只替换camunda,会把命名空间里面也替换
var newXml2 = xml.replace(/camunda:/ig, 'activiti:');newXml2 = newXml2.replace(/FormField/ig, 'formProperty');console.log(newXml2);
后面因为需要表单,用activiti没法解析,找到一个用json做转换的,参考bpmn camunda版转为activiti版
改了一下别人的方法,增加了activiti的属性(本来想在后台把这个方法重写一下,懒得动,先这样吧)
getFormProperty(json) {for (let e in json) {if (e == 'extensionElements' && json.extensionElements.formData && json.extensionElements.formData.formField) {let formProperty = JSON.parse(JSON.stringify(json.extensionElements.formData.formField));if (this.isArrayFn(formProperty)) {formProperty.forEach(x => {x.__prefix = 'activiti';//借用camunda属性 添加activiti的 不然activiti获取不到//对应activiti 表单 namex._name = x._label;//对应activiti 表单 defaultExpressionif (x._defaultValue) {x._default = x._defaultValue;}});} else {formProperty.__prefix = 'activiti';formProperty._name = formProperty._label;if (formProperty._defaultValue) {formProperty._default = formProperty._defaultValue;}}json.extensionElements.formProperty = formProperty;delete json.extensionElements.formData;} else if (e.includes('camunda:')) {let str = e.replace('camunda:', 'activiti:');json[str] = json[e];delete json[e];} else if (typeof json[e] == 'object') {this.getFormProperty(json[e]);}}},
后台代码
开始的时候activiti后台解析总是过不了,直接用xml字符串上传部署,比较方便调试
后台方法,这个disableSchemaValidation()方法不会检查Schema,标签或者属性有的是camunda的也不会报错(比如上面表单的label...)
@PostMapping("/addByString")public R addByString(@RequestParam("xmlBPMN") String xmlBPMN,@RequestParam("deployName") String deployName ) {Deployment deployment = repositoryService.createDeployment()// 这里由于是 xml 的字符串没有资源名称.addString("xmlStr.bpmn", xmlBPMN).disableSchemaValidation() //禁用架构验证.name(deployName).deploy();return R.strData(deployment.getId());}
表单设置好了,查询任务时,根据taskId获取用户任务,activiti7没有了FormService,只能这样
构建表单
@ApiOperation(value = "构建表单")@GetMapping("/getForm")public R getForm(String taskId) {Task task = taskService.createTaskQuery().taskId(taskId).singleResult();if (!Optional.ofNullable(task).isPresent()) {throw new ReturnException("请更新任务");}//获取task对应的表单内容 需要TaskDefinitionKeyUserTask userTask = (UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId()).getFlowElement(task.getTaskDefinitionKey());if (!Optional.ofNullable(userTask).isPresent()) {throw new ReturnException("非用户任务");}//外部表单//String formKey = userTask.getFormKey();List<FormProperty> formProperties = userTask.getFormProperties();if (CollectionUtils.isEmpty(formProperties)) {throw new ReturnException("无表单");}List<Map<String, Object>> collect = formProperties.stream().map(formProperty -> {return CreateMap.build().setAttribute("id", formProperty.getId()).setAttribute("type", formProperty.getType())//在camunda叫做label 前端转json加了一个name属性 不然取不到值.setAttribute("name", formProperty.getName())//在camunda叫做defaultValue activiti表单的default 前端转json加了一个default属性 后台对应defaultExpression.setAttribute("defaultValue", formProperty.getDefaultExpression())//type = enum 枚举类型会用//.setAttribute("formValues", formProperty.getFormValues())//下面没有值 camunda和activiti表单有区别//.setAttribute("variable", formProperty.getVariable())//.setAttribute("expression", formProperty.getExpression())//.setAttribute("datePattern", formProperty.getDatePattern()).build();}).collect(Collectors.toList());return R.okList(collect);}
取出表单填写的值
@ApiOperation(value = "构建表单,取出表单填写的值")@GetMapping("/getFormData")public R getFormData(String taskId) {Task task = taskService.createTaskQuery().taskId(taskId).singleResult();if (!Optional.ofNullable(task).isPresent()) {throw new ReturnException("请更新任务");}//获取task对应的表单内容 需要TaskDefinitionKeyUserTask userTask = (UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId()).getFlowElement(task.getTaskDefinitionKey());if (!Optional.ofNullable(userTask).isPresent()) {throw new ReturnException("非用户任务");}List<FormProperty> formProperties = userTask.getFormProperties();if (CollectionUtils.isEmpty(formProperties)) {throw new ReturnException("无表单");}//查询表单数据List<ActivitiFormdata> formdataList = activitiFormdataDao.findAllByTaskId(taskId);if (CollectionUtils.isEmpty(formdataList)) {//还没有保存表单formProperties.forEach(formProperty -> {ActivitiFormdata one = new ActivitiFormdata();one.setFormPropertyId(formProperty.getId());one.setType(formProperty.getType());if ("enum".equals(formProperty.getType())) {Map<String, String> map = formProperty.getFormValues().stream().collect(Collectors.toMap(FormValue::getId, FormValue::getName));one.setFormValues(map);}one.setName(formProperty.getName());one.setDefaultValue(formProperty.getDefaultExpression());one.setTaskId(taskId);formdataList.add(one);});} else {formProperties.forEach(formProperty -> {formdataList.stream().filter(one -> formProperty.getId().equals(one.getFormPropertyId())).forEach(one -> {one.setFormPropertyId(formProperty.getId());one.setType(formProperty.getType());if ("enum".equals(formProperty.getType())) {Map<String, String> map = formProperty.getFormValues().stream().collect(Collectors.toMap(FormValue::getId, FormValue::getName));one.setFormValues(map);}one.setName(formProperty.getName());one.setDefaultValue(formProperty.getDefaultExpression());});});}return R.okList(formdataList);}
填写表单,根据返回的表单类型判断渲染就行,自定义类型还没搞,不知道怎么用,貌似activiti没有自定义的这种
吐槽:
公司的开发4组之前买了一个工作流,集成到项目被客户否了,正好我们经理想给报表模块做个审核流程,然后任务分给了准备离职的我(天天写bug没意思,想去搞点别的),本来没想搞到公司项目(直接拿人家项目跑一下就行,要用的时候再说),经理要集成到他的报表项目,没法子只能先集成到我项目里了,也算是从头到尾搞了一遍,差不多两周时间,中间也弄了些其他的.
之所以后台用activiti,一个是之前接触过,二是没想到前端部分也叫我弄,前端没写过多少,不熟,还好别人都写好了,直接粘贴,看着改,对比camunda和activiti的xml花了比较久,如果是新项目,前端用bpmn的话,后台用camunda会方便一点吧,camunda是从activiti分出来的,很多地方都一样
第一次写博客,从毕业论文之后就没写过这种东西了
最后,言多必失,好好工作,写bug也得认真,吐槽的话就留给自己或者好友吧
轻喷
//_ooOoo_ ////o8888888o////88" . "88////(| ^_^ |)////O\ = /O//// ____/`---'\____ ////.' \\||// `.//// / \\||| : |||// \ //// / _||||| -:- |||||- \ //// | | \\\ - /// | | //// | \_| ''\---/'' | | //// \ .-\__ `-` ___/-. / ////___`. .' /--.--\ `. . ___ //// ."" '< `.___\_<|>_/___.' >'"". //// | | : `- \`.;`\ _ /`;.`/ - ` : | | //// \ \ `-. \_ __\ /__ _/ .-` / / ////========`-.____`-.___\_____/___.-`____.-'======== //// `=---='////^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //// 佛祖保佑 永不宕机永无BUG //