基于若依开发管理项目中引入工作流引擎activiti7,包含前后端(原创)

您所在的位置:网站首页 若依vue教程 基于若依开发管理项目中引入工作流引擎activiti7,包含前后端(原创)

基于若依开发管理项目中引入工作流引擎activiti7,包含前后端(原创)

2023-10-08 15:00| 来源: 网络整理| 查看: 265

原项目中用到了工作流引擎,使用若依框架开发, 原二开使用项目:https://gitee.com/y_project/RuoYi-Vue 基于activiti7地址:https://gitee.com/smell2/ruoyi-vue-activiti 导入模块到原二开项目中

使用步骤 admin导入bpmn文件,或者绘制工作流,并激活。post为普通员工进入我的审批可以查看所有审批(经销商角色)数据会存入历史表和任务表 在这里插入图片描述历史表展现全部任务数据,task会展示对应岗位的相应条数据登录商管账号,进入代办任务进行审批,通过则状态变为“待财务审核”,进入下一节点;不通过则状态变为“审核失败”,流转结束登录财务账号,进入代办任务进行审批,通过则状态变为“审核成功”,流转结束;不通过则状态变为“审核失败”,流转结束经销商可以查看审批,流转完成 注意事项

在这里插入图片描述 工作流程菜单在开发工具查看(没有的话需要事先插入db和相关代码),在线绘制进行bpmn格式的流程图,部署流程导入已经存在的bpmn文件,根据流程key,在java中进行声明和调用该key即可正式使用该工作流

后端相关

activiti7相关依赖

org.activiti activiti-spring-boot-starter 7.1.0.M4 org.activiti.dependencies activiti-dependencies 7.1.0.M4 pom

解决依赖冲突 activiti7新版本会和mybatis和spring security起冲突,如果项目使用shiro等安全框架会有更大的适配问题 原项目有spring security的情况下需加上配置文件属性

main: allow-bean-definition-overriding: true

activity配置文件属性如下,包括检测流程定义,自动更新和生成db系统表,历史数据级别和使用等

activiti: check-process-definitions: false database-schema-update: true history-level: full db-history-used: true

db依赖问题或mybatis版本冲突解决方法如下

org.mybatis mybatis

数据源配置,给url加上如下后缀属性

%2B8&nullCatalogMeansCurrent=true

完整url格式如下:

url: jdbc:mysql://localhost:99999/test11111111?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true

在使用mysql-connect 8.+以上版本的时候需要添加***nullCatalogMeansCurrent=true***参数,否则在使用mybatis-generator生成表对应的xml等时会扫描整个服务器里面的全部数据库中的表,而不是扫描对应数据库的表。因此mysql会扫描所有的库来找表,如果其他库中有相同名称的表,activiti就以为找到了,本质上这个表在当前数据库中并不存在。

接口

流程部署 在这里插入图片描述

可以将bpmn文件放在resource下的processes目录下,activiti启动的时候会自动加载该目录下的bpmn文件,或者通过调用接口方式部署:

上传文件部署 @PostMapping("/uploadFileAndDeployment") public BaseResponse uploadFileAndDeployment( @RequestParam("processFile")MultipartFile processFile, @RequestParam(value = "processName",required = false) String processName){ String originalFilename = processFile.getOriginalFilename(); String extension = FilenameUtils.getExtension(originalFilename); if (processName != null){ processName = originalFilename; } try { InputStream inputStream = processFile.getInputStream(); Deployment deployment = null; if ("zip".equals(extension)){ // 压缩包部署方式 ZipInputStream zipInputStream = new ZipInputStream(inputStream); deployment = repositoryService.createDeployment().addZipInputStream(zipInputStream).name(processName).deploy(); }else if ("bpmn".equals(extension)){ // bpmn文件部署方式 deployment = repositoryService.createDeployment().addInputStream(originalFilename,inputStream).name(processName).deploy(); } return BaseResponse.success(deployment); } catch (IOException e) { e.printStackTrace(); } return BaseResponse.success(); } 上传BPMN内容字符串部署 @PostMapping("/postBPMNAndDeployment") public BaseResponse postBPMNAndDeployment(@RequestBody AddXMLRequest addXMLRequest){ Deployment deploy = repositoryService.createDeployment() // .addString 第一次参数的名字如果没有添加.bpmn的话,不会插入到 ACT_RE_DEPLOYMENT 表中 .addString(addXMLRequest.getProcessName()+".bpmn", addXMLRequest.getBpmnContent()) .name(addXMLRequest.getProcessName()) .deploy(); return BaseResponse.success(deploy); } 获取流程资源文件 @GetMapping("/getProcessDefineXML") public void getProcessDefineXML(String deploymentId, String resourceName, HttpServletResponse response){ try { InputStream inputStream = repositoryService.getResourceAsStream(deploymentId,resourceName); int count = inputStream.available(); byte[] bytes = new byte[count]; response.setContentType("text/xml"); OutputStream outputStream = response.getOutputStream(); while (inputStream.read(bytes) != -1) { outputStream.write(bytes); } inputStream.close(); } catch (Exception e) { e.toString(); } }

流程实例

启动 @PostMapping("/startProcess") public BaseResponse startProcess( String processDefinitionKey, String instanceName, @AuthenticationPrincipal LocalUserDetail userDetail){ ProcessInstance processInstance = null; try{ StartProcessPayload startProcessPayload = ProcessPayloadBuilder.start().withProcessDefinitionKey(processDefinitionKey) .withBusinessKey("businessKey") .withVariable("sponsor",userDetail.getUsername()) .withName(instanceName).build(); processInstance = processRuntime.start(startProcessPayload); }catch (Exception e){ System.out.println(e); return BaseResponse.error("开启失败:"+e.getLocalizedMessage()); } return BaseResponse.success(processInstance); } 挂起 @PostMapping("/suspendInstance/{instanceId}") public BaseResponse suspendInstance(@PathVariable String instanceId){ ProcessInstance processInstance = processRuntime.suspend(ProcessPayloadBuilder.suspend().withProcessInstanceId(instanceId).build()); return BaseResponse.success(processInstance); } 激活 @PostMapping("/resumeInstance/{instanceId}") public BaseResponse resumeInstance(@PathVariable String instanceId){ ProcessInstance processInstance = processRuntime .resume(ProcessPayloadBuilder.resume().withProcessInstanceId(instanceId).build()); return BaseResponse.success(processInstance); }

任务数据

通过taskid完成任务 @PostMapping("/completeTask/{taskId}") public BaseResponse completeTask(@PathVariable String taskId){ Task task = taskRuntime.task(taskId); if (task.getAssignee()==null){ // 说明任务需要拾取 taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(taskId).build()); } taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskId).build()); return BaseResponse.success(); } 获取自己的任务(与鉴权机制挂钩) @GetMapping("/getTasks") public BaseResponse getTasks(){ Page taskPage = taskRuntime.tasks(Pageable.of(0, 100)); List tasks = taskPage.getContent(); List taskVOS = new ArrayList(); for (Task task : tasks) { TaskVO taskVO = TaskVO.of(task); ProcessInstance instance = processRuntime.processInstance(task.getProcessInstanceId()); taskVO.setInstanceName(instance.getName()); taskVOS.add(taskVO); } return BaseResponse.success(taskVOS); }

历史数据

查询 public List getProcessHistoryByBusinessKey(String businessKey) { ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult(); List historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(instance.getId()) .orderByHistoricActivityInstanceStartTime().asc().list(); List historicActivityInstanceVOList = new ArrayList(); historicActivityInstanceList.forEach(historicActivityInstance -> historicActivityInstanceVOList.add(VOConverter.getHistoricActivityInstanceVO(historicActivityInstance))); return historicActivityInstanceVOList; } 详情查询 HistoricDetailQuery historicDetailQuery = historyService.createHistoricDetailQuery(); List historicDetails = historicDetailQuery.processInstanceId(instanceId).orderByTime().list(); for (HistoricDetail hd: historicDetails) { System.out.println("流程实例ID:"+hd.getProcessInstanceId()); System.out.println("活动实例ID:"+hd.getActivityInstanceId()); System.out.println("执行ID:"+hd.getTaskId()); System.out.println("记录时间:"+hd.getTime()); } 历史流程实例查询 HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery(); List processInstances = historicProcessInstanceQuery.processDefinitionId(processDefinitionId).list(); for (HistoricProcessInstance hpi : processInstances) { System.out.println("业务ID:"+hpi.getBusinessKey()); System.out.println("流程定义ID:"+hpi.getProcessDefinitionId()); System.out.println("流程定义Key:"+hpi.getProcessDefinitionKey()); System.out.println("流程定义名称:"+hpi.getProcessDefinitionName()); System.out.println("流程定义版本:"+hpi.getProcessDefinitionVersion()); System.out.println("流程部署ID:"+hpi.getDeploymentId()); System.out.println("开始时间:"+hpi.getStartTime()); System.out.println("结束时间:"+hpi.getEndTime()); } 任务历史查询(某一次流程的执行经历的多少任务) HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery(); List taskInstances = historicTaskInstanceQuery.taskId(taskId).list(); for (HistoricTaskInstance hti : taskInstances) { System.out.println("开始时间:"+hti.getStartTime()); System.out.println("结束时间:"+hti.getEndTime()); System.out.println("任务拾取时间:"+hti.getClaimTime()); System.out.println("删除原因:"+hti.getDeleteReason()); }

github例子:https://github.com/Activiti/activiti-examples

鉴权机制

官方security轮子

@Component public class SecurityUtil { // 模拟调用了SpringSecurity 登录鉴权 private Logger logger = LoggerFactory.getLogger(SecurityUtil.class); @Autowired private UserDetailsService userDetailsService; public void logInAs(String username) { UserDetails user = userDetailsService.loadUserByUsername(username); if (user == null) { throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user"); } logger.info("> Logged in as: " + username); SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() { @Override public Collection getAuthorities() { return user.getAuthorities(); } @Override public Object getCredentials() { return user.getPassword(); } @Override public Object getDetails() { return user; } @Override public Object getPrincipal() { return user; } @Override public boolean isAuthenticated() { return true; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { } @Override public String getName() { return user.getUsername(); } })); org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); } }

与原项目spring security整合 新api包括taskRuntime和processRuntime都会强制使用security,源码使用了如下注解: @PreAuthorize(“hasRole(‘ACTIVITI_USER’)”) 直接使用接口会导致无权限不允许访问

activiti7中对原有的一些接口做了二次封装,从而进一步简化了用户的使用流程。 通过查看这个两个API的实现类源码来看,调用的话需要调用的用户含有ACTIVITI_USER角色权限。所以,如果没有使用SpringSecurity的话,这两个API便不能直接调用。

先在用户验证处理中插入GROUP_的岗位post和加入ACTIVITI_USER的role public UserDetails createLoginUser(SysUser user) { Set postCode = sysPostService.selectPostCodeByUserId(user.getUserId()); postCode = postCode.parallelStream().map( s -> "GROUP_" + s).collect(Collectors.toSet()); postCode.add("ROLE_ACTIVITI_USER"); List collect = postCode.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()); return new LoginUser(user, permissionService.getMenuPermission(user), collect); //return new LoginUser(user, permissionService.getMenuPermission(user)); } 在login controller中,每次登录的时候鉴权 // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password));

最后将信息放入token中处理

if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { tokenService.verifyToken(loginUser); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } SecurityConfig也别忘记配 httpSecurity // CSRF禁用,因为不使用session .csrf().disable() // 认证失败处理类 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // 过滤请求 .authorizeRequests() // 对于登录login 验证码captchaImage 允许匿名访问 .antMatchers("/ssologin/token", "/ssologin","/login", "/captchaImage","/getssourl","/ssologin").anonymous() .antMatchers( HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js" ).permitAll() .antMatchers("/processDefinition/**").permitAll() .antMatchers("/activitiHistory/**").permitAll() .antMatchers("/profile/**").anonymous() .antMatchers("/common/download**").anonymous() .antMatchers("/common/download/resource**").anonymous() .antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-resources/**").anonymous() .antMatchers("/webjars/**").anonymous() .antMatchers("/*/api-docs").anonymous() .antMatchers("/druid/**").anonymous() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated() .and() .headers().frameOptions().disable(); httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); // 添加JWT filter httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

查询当前用户任务

taskservice方法 //1.得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.得到TaskService对象 TaskService taskService = processEngine.getTaskService(); //3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询 Task task = taskService.createTaskQuery() .processDefinitionKey("holiday") .taskAssignee(SecurityUtils.getUsername() //通过鉴权拿到当前角色) .singleResult(); //4.任务列表的展示 System.out.println("流程实例ID:"+task.getProcessInstanceId()); System.out.println("任务ID:"+task.getId()); //5002 System.out.println("任务负责人:"+task.getAssignee()); System.out.println("任务名称:"+task.getName()); taskruntime方法(新版本api) 通过taskRuntime.tasks获取任务列表并分页,在实例化一条工作流后,activiti会将数据存到ACT_RU_TASK和ACT_RU_IDENTITYLINK表中,IDENTITYLINK通过task_id作为外键关联TASK表,IDENTITYLINK根据用户身份鉴别相应的角色,通过GROUP_ID筛选出对应数据,TYPE_字段则显示该角色是参与者还是贡献者,db如下: 在这里插入图片描述 若衣源码: @Override public Page selectProcessDefinitionList(PageDomain pageDomain) { Page list = new Page(); org.activiti.api.runtime.shared.query.Page pageTasks = taskRuntime.tasks(Pageable.of((pageDomain.getPageNum() - 1) * pageDomain.getPageSize(), pageDomain.getPageSize())); List tasks = pageTasks.getContent(); int totalItems = pageTasks.getTotalItems(); list.setTotal(totalItems); if (totalItems != 0) { Set processInstanceIdIds = tasks.parallelStream().map(t -> t.getProcessInstanceId()).collect(Collectors.toSet()); List processInstanceList = runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIdIds).list(); List actTaskDTOS = tasks.stream() .map(t -> new ActTaskDTO(t, processInstanceList.parallelStream().filter(pi -> t.getProcessInstanceId().equals(pi.getId())).findAny().get())) .collect(Collectors.toList()); list.addAll(actTaskDTOS); } return list; } 实际项目遇到的问题及解决方法

Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘VERSION_’ in ‘field list’ Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘PROJECT_RELEASE_VERSION_’ in ‘field list’ 原因: 创建表缺少VERSION_字段 添加两个字段。

新版bug问题解决

alter table ACT_RE_DEPLOYMENT add column PROJECT_RELEASE_VERSION_ varchar(255) DEFAULT NULL; alter table ACT_RE_DEPLOYMENT add column VERSION_ varchar(255) DEFAULT NULL;

在这里插入图片描述

前端相关

基于BPMN2.0的工作流 demo实例https://demo.bpmn.io/s/start 节点如下,教程https://www.jianshu.com/p/a8a21870986a 在这里插入图片描述

审批流程图: 在这里插入图片描述 bpmnjs引入 导入相关代码,包括我的审批,代办任务,历史流程等的表单设计和模块划分,其他的调整等



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3