看了网上一些文章,动手操作了一遍,终于学会了Activit的一些常规使用。
一、Eclipse中的Activiti插件安装
Activiti有一个Eclipse插件,Activiti Eclipse Designer,可用于图形化建模、测试、部署 BPMN 2.0的流程。这样就不
用我们自己去编写繁琐的流程文件了。具体安装方法见手册。
打开 Help-> Install New Software.在如下面板中 , 点击 Add 按钮, 然后填入下列字段:
Name: Activiti BPMN 2.0 designer
Location: http://activiti.org/designer/update/
然后一步步的安装就可以了。
点击Window--->Preferences--->Activiti--->Save Actions:将Create process definition image when saving the diagram勾选,然后保存bpmn文件的时候会自动截图。
二、实现一个请假的工作流
1、随便模拟一个请假流程:
(1)发起请假申请;
(2)经理审批:
a.如果指定时间内没审批,则自动通过;
b.审批同意;
c.审批不同意;
(3)总经理审批:
a.如果请假大于或等于3天,则总经理审批;
b.如果请假小于3天,则自动通过;
(4)流程结束。
2、新建一个Activiti Diagram文件,按照第1步的思路,拖拉控件,画流程图,并设置相关节点或连线的值
3、配置文件activiti.cfg.xml的参数,主要配置数据库的相关参数等
<? xml version="1.0" encoding="UTF-8"?> < beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> < bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> < property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" /> < property name="jdbcDriver" value="com.mysql.jdbc.Driver" /> < property name="jdbcUsername" value="root" /> < property name="jdbcPassword" value="" /> < property name="databaseSchemaUpdate" value="true" /> < property name="jobExecutorActivate" value="true" /> < property name="activityFontName" value="宋体"/> < property name="labelFontName" value="宋体"/> </ bean > </ beans > |
4、代码实例,实现启动流程、审批、画历史流程图等等,详细见代码中注释
package com.sc.springmvc.controller; import java.io.File; import java.io.InputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.zip.ZipInputStream; import org.activiti.bpmn.model.BpmnModel; import org.activiti.engine.HistoryService; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngineConfiguration; import org.activiti.engine.ProcessEngines; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.repository.ProcessDefinitionQuery; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.image.ProcessDiagramGenerator; import org.apache.commons.io.FileUtils; public class ActivitiDemo { public static void main(String[] args) { ActivitiDemo demo = new ActivitiDemo(); //demo.deploy(); //demo.queryProcdef(); //demo.startFlow(); //demo.queryTask_manager(); //demo.startTask_manager(); //demo.queryTask_boss(); //demo.startTask_boss(); //demo.queryStatus(); //demo.historyTaskList(); //demo.processState(); //获取流程变量 //System.out.println("生成流程图:"); demo.generateImage( "12501" ); } String currUser = "0001" ; String applyUser = "0002" ; String manager = "0003" ; String boss = "0005" ; ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 发布流程 * 发布流程后,流程文件会保存到数据库中 ,插入表act_re_deployment、act_re_procdef */ void deploy(){ RepositoryService repositoryService = processEngine.getRepositoryService(); //手动将myleave.bpmn和myleave.png打包成myleave.zip文件(一定要是zip别压缩成rar) //获取在classpath下的流程文件 InputStream in = this .getClass().getClassLoader().getResourceAsStream( "myleave.zip" ); ZipInputStream zipInputStream = new ZipInputStream(in); //使用deploy方法发布流程:如果是首次表不存在则生成23张表,往这4张表插入流程相关信息:act_ge_bytearray、 act_ge_property、act_re_deployment、act_re_procdef repositoryService.createDeployment() .addZipInputStream(zipInputStream) .name( "myleave" ) .deploy(); } //获取详细的流程定义信息 void queryProcdef(){ RepositoryService repositoryService = processEngine.getRepositoryService(); //创建查询对象 ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); //添加查询条件 query.processDefinitionKey( "leaveApply" ); //通过key获取 // .processDefinitionName("请假申请")//通过name获取 // .orderByProcessDefinitionId()//根据ID排序 //执行查询获取流程定义明细 List<ProcessDefinition> pds = query.list(); for (ProcessDefinition pd : pds) { System.out.println( "ID:" +pd.getId()+ ",NAME:" +pd.getName()+ ",KEY:" +pd.getKey()+ ",VERSION:" +pd.getVersion()+ ",RESOURCE_NAME:" +pd.getResourceName()+ ",DGRM_RESOURCE_NAME:" +pd.getDiagramResourceName()); } } //发布流程 public void startFlow(){ RuntimeService runtimeService = processEngine.getRuntimeService(); //IdentityService identityService = processEngine.getIdentityService(); // 用来设置启动流程的人员ID,引擎会自动把用户ID保存到activiti:initiator中 //identityService.setAuthenticatedUserId(currUser); /** * 启动请假单流程 并获取流程实例 * 因为该请假单流程可以会启动多个所以每启动一个请假单流程都会在数据库中插入一条新版本的流程数据 * 通过key启动的流程就是当前key下最新版本的流程 * 当流程发布后在 act_ru_task ,act_ru_execution, act_ru_identitylink 表中插入流程数据 * */ Map<String, Object> variables = new HashMap<String, Object>(); variables.put( "applyUser" , applyUser); variables.put( "manager" , manager); variables.put( "days" , "5" ); Date date = addTime( new Date(), 0 , 0 , 1 ); variables.put( "dateTime" , date); //到期时间 variables.put( "boss" , boss); String businessKey = "10001" ; //保存于表act_hi_procinst中 //参数businessKey, variables为可选参数 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( "leaveApply" , businessKey, variables); System.out.println( "id:" +processInstance.getId()+ ",activitiId:" +processInstance.getActivityId()); } //增加时间 public static Date addTime(Date date, int day, int hour, int minute){ Calendar ca=Calendar.getInstance(); ca.setTime(date); ca.add(Calendar.DATE, day); ca.add(Calendar.HOUR_OF_DAY, hour); ca.add(Calendar.MINUTE, minute); return ca.getTime(); } /** * 传入Data类型日期,返回字符串类型时间(ISO8601标准时间) * @param date * @return */ public static String getISO8601Timestamp(Date date){ TimeZone tz = TimeZone.getTimeZone( "UTC" ); DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" ); df.setTimeZone(tz); String nowAsISO = df.format(date); return nowAsISO; } /** * 查看任务 */ void queryTask_manager(){ //获取任务服务对象 TaskService taskService = processEngine.getTaskService(); //根据接受人获取该用户的任务 ,表act_ru_task的字段assignee_为待办人 List<org.activiti.engine.task.Task> tasks = taskService.createTaskQuery().processDefinitionKey( "leaveApply" ).taskAssignee(manager).list(); for (org.activiti.engine.task.Task task : tasks) { //ID : 表act_hi_actinst的字段act_id_ System.out.println( "ID:" +task.getId()+ ",姓名:" +task.getName()+ ",接收人:" +task.getAssignee()+ ",开始时间:" +task.getCreateTime()); //获取流程变量 System.out.println( "Variables:" ); RuntimeService runtimeService=processEngine.getRuntimeService(); String excutionId = task.getExecutionId(); String applyUser = String.valueOf(runtimeService.getVariable(excutionId, "applyUser" )); String days = String.valueOf(runtimeService.getVariable(excutionId, "days" )); String dateTime = String.valueOf(runtimeService.getVariable(excutionId, "dateTime" )); System.out.println( "applyUser:" + applyUser + ",days:" + days + ",datetime:" + dateTime); } } /** * 启动流程:经理审批 */ void startTask_manager(){ TaskService taskService = processEngine.getTaskService(); List<org.activiti.engine.task.Task> tasks = taskService.createTaskQuery().taskAssignee(manager).list(); for (org.activiti.engine.task.Task task : tasks) { //taskId 就是查询任务中的 ID String taskId = task.getId(); Map<String, Object> variables = new HashMap<String, Object>(); variables.put( "managerPass" , "1" ); variables.put( "boss" , boss); taskService.complete(taskId, variables); } } /** * 老板查看任务 */ void queryTask_boss(){ //获取任务服务对象 TaskService taskService = processEngine.getTaskService(); //根据接受人获取该用户的任务 List<org.activiti.engine.task.Task> tasks = taskService.createTaskQuery().taskAssignee(boss).list(); for (org.activiti.engine.task.Task task : tasks) { System.out.println( "ID:" +task.getId()+ ",姓名:" +task.getName()+ ",接收人:" +task.getAssignee()+ ",开始时间:" +task.getCreateTime()); //获取流程变量 System.out.println( "Variables:" ); RuntimeService runtimeService=processEngine.getRuntimeService(); String excutionId = task.getExecutionId(); String applyUser = String.valueOf(runtimeService.getVariable(excutionId, "applyUser" )); String manager = String.valueOf(runtimeService.getVariable(excutionId, "manager" )); String managerPass = String.valueOf(runtimeService.getVariable(excutionId, "managerPass" )); String days = String.valueOf(runtimeService.getVariable(excutionId, "days" )); System.out.println( "applyUser:" + applyUser + ",manager:" + manager + ",days:" + days + ",managerPass:" + managerPass); } } /** * 老板启动流程 */ void startTask_boss(){ TaskService taskService = processEngine.getTaskService(); //根据接受人获取该用户的任务 List<org.activiti.engine.task.Task> tasks = taskService.createTaskQuery().taskAssignee(boss).list(); for (org.activiti.engine.task.Task task : tasks) { //taskId 就是查询任务中的 ID String taskId = task.getId(); //完成请假申请任务 taskService.complete(taskId ); } } void queryStatus(){ HistoryService historyService = processEngine.getHistoryService(); HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId( "20001" ).singleResult(); System.out.println( "Process instance end time: " + getDate(historicProcessInstance.getEndTime())); } /** * 历史任务查询:历史活动包括所有节点(流程图圆圈)和任务(流程图矩形),而历史任务只包含任务 */ public void historyTaskList(){ List<HistoricTaskInstance> list=processEngine.getHistoryService() // 历史相关Service .createHistoricTaskInstanceQuery() // 创建历史任务实例查询 .processInstanceId( "37501" ) // 用流程实例id查询 .finished() // 查询已经完成的任务 .list(); for (HistoricTaskInstance hti:list){ System.out.println( "任务ID:" +hti.getId()); System.out.println( "流程实例ID:" +hti.getProcessInstanceId()); System.out.println( "任务名称:" +hti.getName()); System.out.println( "办理人:" +hti.getAssignee()); System.out.println( "开始时间:" +hti.getStartTime()); System.out.println( "结束时间:" +hti.getEndTime()); System.out.println( "=================================" ); } } /** * 查询流程状态(正在执行 or 已经执行结束) */ void processState(){ ProcessInstance pi=processEngine.getRuntimeService() // 获取运行时Service .createProcessInstanceQuery() // 创建流程实例查询 .processInstanceId( "37501" ) // 用流程实例id查询 .singleResult(); if (pi!= null ){ System.out.println( "流程正在执行!" ); } else { System.out.println( "流程已经执行结束!" ); } } String getDate(Date d){ SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); String s = sdf.format(d); return s; } /**https://blog.csdn.net/u011277123/article/details/77380787 * 流程图对历史节点进行高亮显示 * @param processInstanceId * @return */ void generateImage(String processInstanceId) { HistoryService historyService = processEngine.getHistoryService(); RepositoryService repositoryService = processEngine.getRepositoryService(); ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration(); //获取历史流程实例 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); //获取流程图 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); processEngineConfiguration = processEngine.getProcessEngineConfiguration(); Context.setProcessEngineConfiguration((ProcessEngineConfigurationImpl) processEngineConfiguration); ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator(); ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId()); List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); //高亮环节id集合 List<String> highLightedActivitis = new ArrayList<String>(); //高亮线路id集合 List<String> highLightedFlows = getHighLightedFlows(definitionEntity,highLightedActivitList); // 已执行完的任务节点 List<HistoricActivityInstance> finishedInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).finished().list(); for (HistoricActivityInstance hai : finishedInstances) { highLightedActivitis.add(hai.getActivityId()); } //中文显示的是口口口,设置字体就好了 InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png" , highLightedActivitis,highLightedFlows, "宋体" , "宋体" , null , 1.0 ); //单独返回流程图,不高亮显示 // InputStream imageStream = diagramGenerator.generatePngDiagram(bpmnModel); // 输出资源内容到相应对象 try { //生成本地图片 File file = new File( "D:/test3.png" ); FileUtils.copyInputStreamToFile(imageStream, file); } catch (Exception e) { throw new RuntimeException( "生成流程图异常!" , e); } finally { } } /** * 获取需要高亮的线 * @param processDefinitionEntity * @param historicActivityInstances * @return */ private List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) { List<String> highFlows = new ArrayList<String>(); // 用以保存高亮的线flowId for ( int i = 0 ; i < historicActivityInstances.size() - 1 ; i++) { // 对历史流程节点进行遍历 ActivityImpl activityImpl = processDefinitionEntity.findActivity(historicActivityInstances.get(i).getActivityId()); // 得到节点定义的详细信息 List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>(); // 用以保存后需开始时间相同的节点 ActivityImpl sameActivityImpl1 = processDefinitionEntity.findActivity(historicActivityInstances.get(i + 1 ).getActivityId()); // 将后面第一个节点放在时间相同节点的集合里 sameStartTimeNodes.add(sameActivityImpl1); for ( int j = i + 1 ; j < historicActivityInstances.size() - 1 ; j++) { HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j); // 后续第一个节点 HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1 ); // 后续第二个节点 if (activityImpl1.getStartTime().equals(activityImpl2.getStartTime())) { // 如果第一个节点和第二个节点开始时间相同保存 ActivityImpl sameActivityImpl2 = processDefinitionEntity.findActivity(activityImpl2.getActivityId()); sameStartTimeNodes.add(sameActivityImpl2); } else { // 有不相同跳出循环 break ; } } List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions(); // 取出节点的所有出去的线 for (PvmTransition pvmTransition : pvmTransitions) { // 对所有的线进行遍历 ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition.getDestination(); // 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示 if (sameStartTimeNodes.contains(pvmActivityImpl)) { highFlows.add(pvmTransition.getId()); } } } return highFlows; } } |
附,看过的一些不错Activiti文章:
http://www.mossle.com/docs/activiti/index.html
https://blog.csdn.net/a67474506/article/details/38266129
http://www.cnblogs.com/shyroke/category/1126426.html
----------------------------------------------
2019.3.20补充
实际业务中,activiti工作流的开发过程:
1、画流程图
2、数据库业务表增加一个流程实例字段proc_inst_id_
3、发起流程申请,后台保存逻辑
(1)设置流程下一步审批节点的参数Map<String, Object> variables
(2)插入或更新业务相关表,返回业务表的主键businessKey
(3)发起流程
identityService.setAuthenticatedUserId(variables.get("applyUser").toString());
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("流程名称", businessKey, variables);
String processInstanceId = processInstance.getId();
(4)根据上一步返回的流程实例Id,更新业务表相应字段
4、sql获取待办任务
select
to_char(t.id_) taskId,
w.proc_inst_id_,
t.task_def_key_ taskDefinitionKey,
......
from act_ru_task t
inner join 业务表 w on w.proc_inst_id_ = t.proc_inst_id_
where t.assignee_ = 待办人
5、待办审批前端页面,根据当前是哪个流程节点写不同逻辑
<c:if test="${taskDefinitionKey eq 'xxTask'}">
经理审批......
</c:if>
<c:if test="${taskDefinitionKey eq 'yyTask'}">
老板审批......
</c:if>
6、待办审批的后台保存逻辑
(1)判断权限:任务是否属于当前人;
(2)根据流程节点值taskDefinitionKey,设置流程下一步审批节点的参数Map<String, Object> variables
(3)更新相关业务表
(4)调用工作流方法
String taskId = (String) variables.get("taskId");
identityService.setAuthenticatedUserId(当前用户主键);
taskService.addComment(taskId, 流程实例Id, 审批意见);
this.taskService.complete(taskId, variables);
//TODO判断任务是否结束,是的话则根据实际情况更新业务表
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥