做网站可以不做后端吗,个人网站可以做网上支付吗,石化建设分会网站,邯郸市属于哪个省工作流
当一道流程逻辑需要用到多个表单的提交和多个角色的审核共同完成的时候#xff0c;就可以使用工作流。
工作流一般使用的是第三方技术#xff0c;也就是说别人帮你创建数据库表和service层、mapper层#xff0c;你只需要注入工具接口即可使用。
原理#xff1a;一…工作流
当一道流程逻辑需要用到多个表单的提交和多个角色的审核共同完成的时候就可以使用工作流。
工作流一般使用的是第三方技术也就是说别人帮你创建数据库表和service层、mapper层你只需要注入工具接口即可使用。
原理一切操作都是sql语句第三方做好你只要按照它们的规范即可完成工作流。
Activiti
Activiti是一个开源的轻量级工作流引擎2010年基于jBPM4实现首次开源。官网地址https://www.activiti.org
Activiti可以将业务系统中复杂的业务流程抽取出来使用专门的建模语言BPMN进行定义
BPMN是一种用于图形化表示和描述业务流程的标准化标记语言目前主流的版本是2.0
事件event
事件是业务流程模型中的重要元素之一事件可以发生在流程的任何阶段并且可以影响流程的执行。分为以下几类 开始事件Start Event表示流程的起点通常用于触发流程的启动 结束事件End Event表示流程的结束点通常用于触发流程的结束 活动activiti
任务Task是最基本的活动类型表示一个简单的、可执行的工作单元。任务通常由人工执行并且需要指定执行者
用户任务是由人工执行的需要指定执行的用户或角色并提供相应的输入
手动任务是由系统自动执行的不需要指定执行的用户或角色 流向flow
流是连接两个流程节点的连线。常见的流向包含以下几种 请假工作流案例
在学校中如果有事需要请假一般需要向得到老师批准才可以完成请假。
学生请假的学生需要先填写请假单填写的字段有请假人、请假天数、开始请假时间、请假事由。老师审批员工的请假单如果不同意则需要说明不同意的理由。 同意进入下个任务不同意直接结束该流程实例驳回觉得信息填写不完善需要改善 实现步骤
1、搭建环境使用SpringBoot集成Activiti把初始化环境做出来
2、绘制流程按照BPMN的规范使用流程定义工具用流程符号把整个流程描述出来
3、部署流程把画好的流程定义文件加载到数据库中生成表的数据
4、操作流程使用java代码来操作数据库表中的内容
提示数据库的依赖和配置也需要写但我这里就不写了
添加依赖 !--安全框架 spring security --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependency!--springboot与activiti7整合的starter--dependencygroupIdorg.activiti/groupIdartifactIdactiviti-spring-boot-starter/artifactIdversion7.10.0/version/dependency!--如果activiti依赖下载不了可以配置如下地址进行下载--repositoriesrepositoryidactiviti-releases/idurlhttps://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/url/repository/repositories添加配置文件 activiti:# 记录所有历史数据history-level: full# 是否需要使用历史表,默认false不使用,而配置true是使用历史表db-history-used: true# 关闭流程自动部署需要手动部署流程check-process-definitions: false# 如果部署过程遇到任何问题服务不会失败deployment-mode: never-fail注意需要在自己的MySQL中创建一个新的数据库activiti-db
我们也可以直接查询数据库数据库中创建了25张表目前说明Springboot已成功集成了activiti7 表结构
Activiti 的表都以ACT_ 开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。
ACT_GE GE 表示 general 通用数据ACT_RE RE表示 repository这个前缀的表包含了流程定义信息ACT_RURU表示 runtime这些运行时的表包含流程实例任务变量异步任务等运行中的数据ACT_HIHI表示 history 这些表包含历史数据比如历史流程实例 变量任务等等
表分类表名解释一般数据[ACT_GE_BYTEARRAY]通用的流程定义和流程资源[ACT_GE_PROPERTY]系统相关属性流程历史记录[ACT_HI_ACTINST]历史的活动实例[ACT_HI_ATTACHMENT]历史的流程附件[ACT_HI_COMMENT]历史的说明性信息[ACT_HI_DETAIL]历史的流程运行中的细节信息[ACT_HI_IDENTITYLINK]历史的流程运行过程中用户关系[ACT_HI_PROCINST]历史的流程实例[ACT_HI_TASKINST]历史的任务实例[ACT_HI_VARINST]历史的流程运行中的变量信息流程定义表[ACT_RE_DEPLOYMENT]部署单元信息[ACT_RE_MODEL]模型信息[ACT_RE_PROCDEF]已部署的流程定义运行实例表[ACT_RU_EVENT_SUBSCR]运行时事件[ACT_RU_EXECUTION]运行时流程执行实例[ACT_RU_IDENTITYLINK]运行时用户关系信息存储任务节点与参与者的相关信息[ACT_RU_JOB]运行时作业[ACT_RU_TASK]运行时任务[ACT_RU_VARIABLE]运行时变量表
常见api重点
在activiti7框架内部已经对25张表的数据操作已经封装了对应的service相当于自己开发的25个mapper和25个service RepositoryService用于部署流程定义可以添加、删除、查询和管理流程定义相当与类所有人请假都需要按照这个模板执行。 act_re_deployment流程部署记录每次工作流的部署信息包括部署名称和时间等act_ge_bytearray流程资源表系统会将流程定义的两个文件保存到这张表中act_re_procdef流程定义表记录每个流程定义的信息包括流程定义的名称、版本号、部署ID等 RuntimeService用于启动、查询和管理正在运行的流程实例相当于对象启动之后每个人请假都要按照这个模板走。 act_ru_execution表运行时流程实例表插入一条新的流程实例的执行信息包括流程实例ID、流程定义ID等act_ru_task运行时任务表插入一条新的任务记录表示流程实例的启动任务act_ru_identitylink 运行时身份关联表插入一条新的身份关联记录表示流程实例的启动任务与相关用户的关系 TaskService用于查询和管理当前用户可以操作的任务以及完成任务相当于栈中的方法。 流程启动之后不同的人登录的时候需要查询是否要自己要执行的任务如果有可以对流程进行操作一般是在一个专门的事务待办页面act_ru_execution表中发现新增了【经理审批】待执行的节点而完成的【填写请假单】节点被删除了act_ru_task表中待办任务也变成了李四【经理审批】 一个任务结束后该任务的记录就会在ru_task表中删除会在运行时任务表添加下一条需要执行的任务。act_ru_variable表中会存储代码中传入的表单数据 HistoryService用于查询历史数据例如已完成的流程实例、已删除的流程实例、用户任务等历史统计主要是用于查询比如说统计某个人这个月请了多少假。相当于日志。 act_hi_procinst历史的流程实例act_hi_actinst历史的活动实例**act_hi_taskinst历史的任务实例 ** 任务执行完毕后endTime结束时间会有值。act_hi_identitylink历史流程用户关系act_hi_varinst历史流程运行中的变量信息 开始定义一个类流程模板根据类创建对象流程实例按照栈的顺序流程模板定义的执行类中的每个方法任务在每次操作后都会记录日志历史数据。 因为我们现在使用的是springboot集成了activiti这些api也被spring容器进行了管理需要用到以上api的时候直接注入即可
绘制流程
我们打开bpmn-js可以直接在页面中画图步骤如下
① 定义流程编号ID和名称 ② 新增一个用户任务并指定代理人为张三 ③ 新增一个用户任务并指定代理人为李四同时需要结束这个流程最后需要有一个结束事件 ④ 流程图画好之后在页面的左下角有一个导出就可以直接导出为bpmn文件xml文件 ⑤ 把生成后的bpmn文件改名拷贝到idea中备用存储位置resource/bpmn/qingjia.bpmn
因为保存的文件都是xml文件我们为了方便查看这些流程也可以截个图一起放入bpmn目录下 案例分析
在指派用户任务的执行人时使用的都是直接指派给固定账号这样流程设计审批的灵活性就很差
因此Activiti提供了各种不同的分配方式这章我们就来详细研究下其它任务分配方式主要有表达式分配、监听器分配
表达式分配
值表达式就是使用UEL表达式一种占位符来替换具体的分配人在使用的时候只需要对表达式中的变量进行赋值即可
${user.assignee} 指定对象的属性值为代理人
${student} 指定变量值为代理人
绘制流程
重新绘制前面的流程但是在代理人的位置不再直接写死为张三、李四而是使用 a s s i n g e e 0 、 {assingee0}、 assingee0、{assingee1}来代替 代码实现
/*** author windStop* version 1.0* description 测试含有表达式的代理人* date 2024年08月21日17:37:23*/
SuppressWarnings(ALL)
SpringBootTest
Slf4j
public class ActivitiTestPlus {Autowiredprivate RepositoryService repositoryService;Autowiredprivate RuntimeService runtimeService;Autowiredprivate TaskService taskService;Testpublic void test(){//1. 创建模版部署信息流程定义Deployment deploy repositoryService.createDeployment().addClasspathResource(bpmn/qingjia2.bpmn).addClasspathResource(bpmn/qingjia.png).name(qingjia).deploy();log.info(部署信息{}, deploy);//2. 创建流程实例MapString,Object claim new HashMap();claim.put(student,张三);claim.put(teacher,吴彦祖);ProcessInstance processInstance runtimeService.startProcessInstanceByKey(qingjia,claim);log.info(流程实例信息{},processInstance);//3. 查询自己的待办任务ListTask list taskService.createTaskQuery().processDefinitionKey(qingjia).taskAssignee(张三).list();log.info(自己待办任务集合{},list);//4. 任务一执行请假for (Task task : list) {log.info(正在执行的任务id为{}, task.getId());log.info(正在执行的任务名称为{}, task.getName());//请假原因任务一 需要填写的额外内容一般是前端表单传来的数据(用户自己输入)MapString,Object leaveContent new HashMap();leaveContent.put(username,蔡申友);//请假人leaveContent.put(reason,出差工作..);//请假原因leaveContent.put(startDate,2024年8月21日18:10:53);//请假开始时间leaveContent.put(days,20);//请假天数//完成任务taskService.complete(task.getId(),leaveContent);log.info(任务完成....);}}//老师同意Testpublic void test2(){//5. 任务二老师审批//5.1 查询自己的任务待办集ListTask list taskService.createTaskQuery().processDefinitionKey(qingjia).taskAssignee(吴彦祖).list();log.info(老师的任务集为{},list);//5.2 老师执行任务for (Task task : list) {log.info(正在执行的任务id为{}, task.getId());log.info(正在执行的任务名称为{}, task.getName());//组装业务数据前端传递的MapString,Object claim new HashMap();claim.put(remark,同意请假但要按时返校);taskService.complete(task.getId(),claim);log.info(任务二执行完毕...);}}//老师不同意Testpublic void test3(){//1. 查询老师(当前用户)要处理的任务集在专门的页面展示ListTask tasks taskService.createTaskQuery().processDefinitionKey(qingjia).taskAssignee(吴彦祖).list();//2. 执行任务 - 拒绝任务for (Task task : tasks) {log.info(正在执行的任务id为{}, task.getId());log.info(正在执行的任务名称为{}, task.getName());//组装业务数据前端传递的MapString,Object claim new HashMap();claim.put(approvalStatus, 不同意);claim.put(approvalNode, 时间太久不同意);//记录流程变量runtimeService.setVariables(task.getProcessInstanceId(),claim);//添加流程变量删除流程实例表示任务被拒绝runtimeService.deleteProcessInstance(task.getProcessInstanceId(), 时间太久不同意);log.info(任务二, 拒绝执行完毕...);}}
}
注意
老师不同意则流程会终止执行。如果老师审批不同意那么主任就不用审批了整个流程就应该直接结束。结束调用的方法和同意不同并且变量需要存到流程变量中。
因此不同意则应该是终止流程而不是完成节点在删除流程时同时也把审批不同意及理由作为流程变量存储到流程变量中。
候选人
在前面的流程定义中在任务结点的都是设置了一个负责人但是在企业中每个节点上都可能有多个负责人。
下面我们就需要使用候选人或者候选人组做为身份标识替换掉前面的单个参与者来完成任务。需要将候选人提权成执行人才能执行任务该任务只能让一个人处理。
一个审批节点可能有多个人同时具有审批的权限这时我们就可以通过候选人来处理。
注意候选人默认可以在身份关联表中ru_identtitylink查看
绘制流程
这次绘制流程时对于经理审批不再设置代理人而是设置候选人多个候选人是分隔 流程启动后任务在act_ru_task表中的审批人是空的但是act_ru_identitylink表保存了候选人信息.
拾取任务提权
提升候选人的权限为执行人。 // 测试候选人Testpublic void test(){//1. 创建流程模版Deployment deploy repositoryService.createDeployment().addClasspathResource(bpmn/qingjia3.bpmn).addClasspathResource(bpmn/qingjia.png).name(qingjia).deploy();log.info(模版创建成功{},deploy);//2. 启动流程(创建流程实例)//2.1 填充执行人变量MapString,Object variables new HashMap();variables.put(a1,蔡申友);//填充发起人variables.put(c1,王老师);//候选老师1variables.put(c2,陈老师);//候选老师2variables.put(c3,张老师);//候选老师3//2.2 启动流程ProcessInstance processInstance runtimeService.startProcessInstanceByKey(qingjia,variables);log.info(启动流程成功{},processInstance);//3. 发起人查询自己的任务列表ListTask list taskService.createTaskQuery().list();//4. 发起流程 执行任务一for (Task task : list) {log.info(正在执行的任务id为{}, task.getId());log.info(正在执行的任务名称为{}, task.getName());//请假原因任务一 需要填写的额外内容一般是前端表单传来的数据(用户自己输入)MapString,Object leaveContent new HashMap();leaveContent.put(username,蔡申友);//请假人leaveContent.put(reason,出差工作..);//请假原因leaveContent.put(startDate,2024年8月21日18:10:53);//请假开始时间leaveContent.put(days,20);//请假天数//完成任务taskService.complete(task.getId(),leaveContent);log.info(任务完成....);}}// 候选人之一王老师执行任务Testpublic void test2(){//模拟登录,防止UsernameNotFoundException错误securityUtil.logInAs(王老师);//查询候选人或者执行人 和指定人相同的任务ListTask list taskService.createTaskQuery().taskCandidateOrAssigned(王老师).list();//全部任务拾取: 将指定用户从候选人提升为审批人for (Task task : list) {taskService.claim(task.getId(),王老师);//执行任务MapString,Object map new HashMap();map.put(leavenContend,同意请假);taskService.complete(task.getId(),map);}}归还任务消权/ 交接任务换人
归还任务将执行人列设置为null。
交接任务将执行人的值设置为别人的名称 / id。 //归还拾取的用户不审批了。就放弃审批人的操作//交接拾取任务后如果不想操作那么可以归还任务也可以将任务交接给其他用户Testpublic void test3() {//模拟登录,防止UsernameNotFoundException错误new SecurityUtil().logInAs(王老师);ListTask list taskService.createTaskQuery().taskCandidateOrAssigned(王老师) // 根据 审批人或者候选人 来查询待办任务.list();for (Task task : list) {// 归还操作的本质其实就是设置审批人为空// taskService.unclaim(task.getId());//任务交接taskService.setAssignee(task.getId(), 陈老师);}}候选人组防止新增该权限的人
按照组名查任务该组名一般都是权限 按照组查找任务查找到了然后进行提升权限。
拾取任务
拾取任务的目的是将候选人提升为审批人 //查询经理部门的任务Testpublic void test4() {//模拟登录,防止UsernameNotFoundException错误//new SecurityUtil().logInAs(孙经理);//根据候选人查询任务ListTask list taskService.createTaskQuery().taskCandidateGroup(manageGroup) // 查询经理部门的任务.list();//任务拾取: 将指定用户从候选人提升为审批人for (Task task : list) {taskService.claim(task.getId(), 孙经理);}}其他操作都和上述相似
查询历史任务
历史任务的查询需要使用HistoryService完成主要就是根据各种条件从前面讲过的一堆历史表中查询数据 Autowiredprivate HistoryService historyService;Testpublic void test9(){HistoricTaskInstanceQuery instanceQuery historyService.createHistoricTaskInstanceQuery().includeProcessVariables()//包含流程变量配合下面使用.orderByHistoricTaskInstanceEndTime().desc()//按历史任务实例结束时间排序.finished()//只查询已完成的任务.taskAssignee(张三);//根据执行人查询//自定义流程变量 条件查询//instanceQuery.processVariableValueGreaterThan(days, 1);//查询历史流程ListHistoricTaskInstance list instanceQuery.list();for (HistoricTaskInstance history : list) {System.out.println(Id: history.getId());System.out.println(ProcessInstanceId: history.getProcessInstanceId());System.out.println(StartTime: history.getStartTime());System.out.println(Name: history.getName());MapString, Object processVariables history.getProcessVariables();System.out.println(processVariables.get(days).toString());System.out.println(processVariables.get(reason).toString());System.out.println();}}查询条件API说明
方法名称processInstanceBusinessKey(String processInstanceBusinessKey)根据流程实例业务Key查询taskId(String taskId)根据任务ID查询taskAssignee(String taskAssignee) | taskAssigneeLike(String taskAssignee)根据执行人查询finished()已完成的申请过、同意过unfinished()未完成任务orderByHistoricTaskInstanceEndTime().desc()按照执行时间排序taskName(String var1) | taskNameLike(String var1)根据节点任务名称查询list()返回分页数据includeProcessVariables()包含流程变量配合下面使用processVariableValueEquals(String variableName, Object variableValue)两个值相等processVariableValueNotEquals(String variableName, Object variableValue)两个值不相等processVariableValueGreaterThan(String name, Object value)大于processVariableValueLessThan(String name, Object value)小于
流程网关
网关用于控制流程的执行流向它的作用是在流程执行时进行决策决定流程的下一个执行步骤。Activiti7中有以下几种类型的网关 排他网关用于在流程中进行条件判断根据不同的条件选择不同的分支路径只有满足条件的分支会被执行其他分支会被忽略排除其他路径只走一条。一条同意即可 并行网关用于将流程分成多个并行的分支这些分支可以同时执行当所有分支都执行完毕后流程会继续向下执行全部路径都走都同意才可有一个拒绝直接结束流程实例。 包容网关用于根据多个条件的组合情况选择分支路径可以选择满足任意一个条件的分支执行或者选择满足所有条件的分支执行带条件的只走一条没条件的必走
排他网关
排他网关用于对流程中分支进行决策当执行到达这个网关时会按照所有出口顺序流定义的顺序对它们进行计算选择第一个条件为true的顺序流继续流程。 注意这种设计有一点小bug当有多个条件都符合会选取到第一个为true的路径进行走。
并行网关
并行网关用于将流程分成多个并行的分支这些分支可以同时执行当所有分支都执行完毕后流程会继续向下执行
fork分支并行后的所有外出顺序流为每个顺序流都创建一个并发分支join汇聚 所有到达并行网关在此等待的进入分支直到所有进入顺序流的分支都到达以后 流程就会通过网关 包容网关
包含网关用于根据多个条件的组合情况选择分支路径可以选择满足任意一个条件的分支执行有条件必须执行无条件的必须执行 业务id对接
目前我们已经基本完成了activiti的学习我们发现目前的工作流其实是脱离我们的实际业务存在的
如果想将activiti与实际业务联系起来需要用到它提供的一个字段buinessId这个字段用来记录业务表的主键
我们可以在启动流程的时候设置
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey, MapString, Object variables);你可以直接通过 businessKey 查询到与特定请假申请相关的流程实例。
// 查询请假申请的流程实例
ListHistoricProcessInstance processInstances historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(leaveApplication.getId().toString()).list();for (HistoricProcessInstance instance : processInstances) {System.out.println(Process Instance ID: instance.getId());System.out.println(Start Time: instance.getStartTime());System.out.println(End Time: instance.getEndTime());System.out.println(Status: (instance.getEndTime() null ? Running : Completed));
}通过 businessKey你可以查询到与特定请假申请相关的所有流程实例这有助于审计和监控流程实例的状态。
// 查询请假申请的流程实例
HistoricProcessInstance processInstance historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(leaveApplication.getId().toString()).singleResult();if (processInstance ! null processInstance.getEndTime() ! null) {// 流程实例已完成LeaveStatus status processInstance.getEndTime().isBefore(LocalDate.now()) ? LeaveStatus.APPROVED : LeaveStatus.REJECTED;leaveApplication.setStatus(status);leaveApplicationRepository.save(leaveApplication);
}
通过 businessKey你可以轻松地找到与特定请假申请相关的流程实例并根据流程实例的状态更新请假申请的状态。
注意与执行人查询的区别 查询目的不同 使用执行人查询主要关注当前分配给特定执行人的任务适用于查询当前执行人需要处理的任务。 使用 businessKey 查询关注与特定业务实体相关的所有流程实例适用于审计和监控流程实例的状态。 查询范围不同 使用执行人查询仅限于当前分配给执行人的任务不涉及流程实例的全局状态。 使用 businessKey 查询可以查询到与特定业务实体相关的所有流程实例包括已完成的流程实例。 查询深度不同 使用执行人查询通常只涉及到当前的任务可能需要额外步骤来获取流程实例的信息。 使用 businessKey 查询可以直接获取到流程实例的完整信息包括历史记录。