參考資料:https://juejin.im/post/5aafa3eef265da23784015b9
用戶手冊(英):http://www.mossle.com/docs/activiti/5.21/index.html
用戶手冊(中):http://www.mossle.com/docs/activiti/index.html
論壇:https://www.oschina.net/question/tag/activiti
學習視頻:http://edu.51cto.com/course/11678.html
一、引入依賴pom
<!-- activiti -->
<activiti.version>6.0.0</activiti.version>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-rest</artifactId>
<version>${activiti.version}</version>
</dependency>
二、Activiti與spring整合xml配置文件applicationContext-activiti.xml
xml分爲幾個部分:數據源、事務管理器、引擎配置、引擎工廠、獲取service。(同樣,spring自身的一些配置此處略過。)
<?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.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="auditDataSource" />
<property name="transactionManager" ref="configTransactionManager" />
<property name="databaseSchemaUpdate" value="false" />
<!-- 如果流程中有定時任務再開啓 -->
<property name="asyncExecutorActivate" value="false" />
<!-- 關閉流程定義的內存緩存數量,用redis做緩存 -->
<property name="processDefinitionCacheLimit" value="0" />
<property name="idGenerator">
<!-- 主鍵生成策略,這裏用uuid, 也可以用公司的 id生成策略 -->
<bean class="com.dfire.soa.audit.activiti.frame.MyStrongUuidGenerator" />
</property>
<!--配置全局監聽器-->
<property name="eventListeners">
<list>
<ref bean="globalEventListener"/>
</list>
</property>
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<bean id="repositoryService" factory-bean="processEngine"
factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine"
factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine"
factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine"
factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine"
factory-method="getManagementService" />
<bean id="IdentityService" factory-bean="processEngine"
factory-method="getIdentityService" />
</beans>
@Resource
protected RuntimeService runtimeService;
@Resource
protected TaskService taskService;
@Resource
protected HistoryService historyService;
@Resource
protected IProcessSendService processSendService;
@Resource
protected IProcessInnerService processInnerService;
3.項目目錄結構
1.command命令模式
package com.dfire.soa.audit.activiti.command;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
public class UpdateHiProcessReasonByMarkEndedCommand implements Command {
protected String processInstanceId;
protected String deleteReason;
public UpdateHiProcessReasonByMarkEndedCommand(String processInstanceId, String deleteReason) {
this.processInstanceId = processInstanceId;
this.deleteReason = deleteReason;
}
@Override
public Object execute(CommandContext commandContext) {
HistoricProcessInstanceEntity historicProcessInstanceEntity = commandContext
.getDbSqlSession().selectById(HistoricProcessInstanceEntity.class, processInstanceId);
if (historicProcessInstanceEntity != null) {
historicProcessInstanceEntity.markEnded(deleteReason);
}
return null;
}
}
package com.dfire.soa.audit.activiti.command;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 自定義命令
*/
@Service
public class PersonalCommandManager {
@Resource
private SpringProcessEngineConfiguration processEngineConfiguration;
public void updateHiProcessReasonByMarkEnded(String processInstanceId, String deleteReason) {
processEngineConfiguration.getCommandExecutor().execute(new UpdateHiProcessReasonByMarkEndedCommand(processInstanceId, deleteReason));
}
}
2.frame生成uuid
package com.dfire.soa.audit.activiti.frame;
import org.activiti.engine.impl.persistence.StrongUuidGenerator;
public class UuidGenerator extends StrongUuidGenerator {
@Override
public String getNextId() {
String nextId = super.getNextId();
return nextId.replace("-", "").toUpperCase();
}
}
流程定義裏面的Id可以設置成自己的。
3.handle 處理審覈操作(同意,拒絕,撤銷)
HandlerEnum:
package com.dfire.soa.audit.activiti.handle;
public enum HandleEnum {
AGREE("同意", "agreeHandler"),
CANCEL("撤銷", "cancelHandler"),
DISAGREE("拒絕", "disagreeHandler"),;
HandleEnum(String name, String handler) {
this.name = name;
this.handler = handler;
}
private String name;
private String handler;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHandler() {
return handler;
}
public void setHandler(String handler) {
this.handler = handler;
}
}
IHandle接口
package com.dfire.soa.audit.activiti.handle;
import com.dfire.soa.audit.constants.activiti.HandleContext;
public interface IHandle {
/**
* 審批操作
*/
void handle(String tenantId, String taskId, String processInstanceId, HandleContext handleContext);
}
HandleContext 操作上下文環境
package com.dfire.soa.audit.constants.activiti;
import com.dfire.soa.audit.dto.activiti.attachment.AttachmentDTO;
import java.io.Serializable;
import java.util.Map;
public class HandleContext implements Serializable {
private Map<String, Object> variables; // 流程變量
private Map<String, Object> localVariables; // 當前task本地變量
private String comment; // 評論意見
private AttachmentDTO attachmentDTO; // 附件
public Map<String, Object> getVariables() {
return variables;
}
public void setVariables(Map<String, Object> variables) {
this.variables = variables;
}
public Map<String, Object> getLocalVariables() {
return localVariables;
}
public void setLocalVariables(Map<String, Object> localVariables) {
this.localVariables = localVariables;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public AttachmentDTO getAttachmentDTO() {
return attachmentDTO;
}
public void setAttachmentDTO(AttachmentDTO attachmentDTO) {
this.attachmentDTO = attachmentDTO;
}
}
HandleManager 判斷用哪個handler處理
package com.dfire.soa.audit.activiti.handle;
import com.dfire.soa.audit.constants.activiti.HandleContext;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class HandleManager implements ApplicationContextAware {
@Transactional(rollbackFor = Exception.class)
public void handle(HandleEnum handleEnum, String tenantId, String taskId, HandleContext handleContext) {
handle(handleEnum, tenantId, taskId, null, handleContext);
}
@Transactional(rollbackFor = Exception.class)
public void handle(HandleEnum handleEnum, String tenantId, String taskId, String processInstanceId, HandleContext handleContext) {
IHandle handle = getHandlerFromEnum(handleEnum);
if (handleContext == null) {
handleContext = new HandleContext();
}
handle.handle(tenantId, taskId, processInstanceId, handleContext);
}
private IHandle getHandlerFromEnum(HandleEnum handleEnum) {
return applicationContext.getBean(handleEnum.getHandler(), IHandle.class);
}
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
實現handler
1.AgreeHandler
2.DisAgreeHandler
3.CancleHandler
AgreeHandler
package com.dfire.soa.audit.activiti.handle.handler;
import com.alibaba.fastjson.JSON;
import com.cxx.soa.audit.activiti.handle.BaseHandle;
import com.cxx.soa.audit.activiti.handle.IHandle;
import com.cxx.soa.audit.activiti.util.AuditCheckUtils;
import com.cxx.soa.audit.constants.activiti.HandleContext;
import com.cxx.soa.audit.constants.activiti.StatusEnum;
import com.cxx.soa.audit.constants.activiti.UserTaskCategoryEnum;
import org.activiti.engine.task.Task;
import org.springframework.stereotype.Component;
@Component
public class AgreeHandler implements IHandle {
@Override
public void handle(String tenantId, String taskId, String processInstanceId, HandleContext handleContext) {
//processInstanceId不需要傳值
String errMsg1 = "taskId不能爲空!";
String errMsg2 = "審批不存在或已被撤銷!";
AuditCheckUtils.check(taskId, errMsg1);
Task task = taskService.createTaskQuery().taskId(taskId).taskTenantId(tenantId).singleResult();
AuditCheckUtils.check(task, errMsg2);
if (task.getProcessInstanceId() == null){
throw new BizException("請不要重複操作");
}
//審批者類型 approver ranker
String category = task.getCategory();
processInstanceId = task.getProcessInstanceId();
if (handleContext.getComment() != null) {
taskService.addComment(taskId, processInstanceId, handleContext.getComment());
}
if (handleContext.getVariables() != null) {
taskService.setVariables(taskId, handleContext.getVariables());
}
if (handleContext.getLocalVariables() != null) {
taskService.setVariablesLocal(taskId, handleContext.getLocalVariables());
}
taskService.complete(taskId);
personalCommandManager.updateHiTaskReason(taskId, StatusEnum.PASS.getCode());
//如果是職級,就修改assignee爲當前操作的用戶
if (category.equals(UserTaskCategoryEnum.GROUP.getCode())){
personalCommandManager.updateHiTaskAssigneeAndcategory(taskId, handleContext.getUserId(), UserTaskCategoryEnum.APPROVER.getCode());
}
}
}
DisAgreeHandler
package com.dfire.soa.audit.activiti.handle.handler;
import com.alibaba.fastjson.JSON;
import com.cxx.soa.audit.activiti.handle.BaseHandle;
import com.cxx.soa.audit.activiti.handle.IHandle;
import com.cxx.soa.audit.activiti.util.AuditCheckUtils;
import com.cxx.soa.audit.activiti.util.MsgConvertUtils;
import com.cxx.soa.audit.constants.activiti.HandleContext;
import com.cxx.soa.audit.constants.activiti.StatusEnum;
import com.cxx.soa.audit.constants.activiti.UserTaskCategoryEnum;
import com.cxx.soa.audit.notify.ProcessMsg;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class DisagreeHandler implements IHandle {
@Override
public void handle(String tenantId, String taskId, String processInstanceId, HandleContext handleContext) {
String errMsg1 = "taskId爲空";
String errMsg2 = "審批不存在或已經撤銷";
AuditCheckUtils.check(taskId, errMsg1);
Task task = taskService.createTaskQuery().taskId(taskId).taskTenantId(tenantId).singleResult();
if (task.getProcessInstanceId() == null){
throw new BizException("請不要重複操作");
}
processInstanceId = task.getProcessInstanceId();
AuditCheckUtils.check(task, errMsg2);
//審批者類型 approver ranker
String category = task.getCategory();
if (handleContext.getComment() != null) {
taskService.addComment(taskId, task.getProcessInstanceId(), handleContext.getComment());
}
if (handleContext.getVariables() != null) {
taskService.setVariables(taskId, handleContext.getVariables());
}
if (handleContext.getLocalVariables() != null) {
taskService.setVariablesLocal(taskId, handleContext.getLocalVariables());
}
runtimeService.deleteProcessInstance(task.getProcessInstanceId(), StatusEnum.UN_PASS.getCode());
//如果是職級,就修改assignee爲當前操作的用戶
if (category.equals(UserTaskCategoryEnum.GROUP.getCode())){
personalCommandManager.updateHiTaskAssigneeAndcategory(taskId, handleContext.getUserId(), UserTaskCategoryEnum.APPROVER.getCode());
}
if (handleContext.isSendNotify()) {
//發送消息通知,該審批未通過
}
}
}
CancleHandler
package com.dfire.soa.audit.activiti.handle.handler;
import com.alibaba.fastjson.JSON;
import com.cxx.soa.audit.activiti.handle.BaseHandle;
import com.cxx.soa.audit.activiti.handle.IHandle;
import com.cxx.soa.audit.activiti.util.AuditCheckUtils;
import com.cxx.soa.audit.activiti.util.MsgConvertUtils;
import com.cxx.soa.audit.constants.activiti.HandleContext;
import com.cxx.soa.audit.constants.activiti.StatusEnum;
import com.cxx.soa.audit.notify.ProcessMsg;
import com.cxx.soa.audit.notify.TaskMsg;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.task.Task;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class CancelHandler extends BaseHandle implements IHandle {
@Override
public void handle(String tenantId, String taskId, String processInstanceId, HandleContext handleContext) {
String errMsg1 = "processInstanceId不能爲空";
String errMsg2 = "該記錄不存在或者審批流已經結束!";
AuditCheckUtils.check(processInstanceId, errMsg1);
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId)
.taskTenantId(tenantId).singleResult();
AuditCheckUtils.check(task, errMsg2);
if (handleContext.getComment() != null) {
taskService.addComment(taskId, processInstanceId, handleContext.getComment());
}
if (handleContext.getVariables() != null) {
taskService.setVariables(task.getId(), handleContext.getVariables());
}
if (handleContext.getLocalVariables() != null) {
taskService.setVariablesLocal(task.getId(), handleContext.getLocalVariables());
}
// 獲取當前有多少個正在審批的
List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery().taskTenantId(tenantId).processInstanceId(processInstanceId).list();
List<TaskMsg> taskMsgs = MsgConvertUtils.cvtTaskMsgs(tasks);
//刪除當前撤銷的task記錄
Task runTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
runtimeService.deleteProcessInstance(task.getProcessInstanceId(), StatusEnum.CANCEL.getCode());
if (runTask != null) {
historyService.deleteHistoricTaskInstance(runTask.getId());
logger.info("【刪除撤銷最後的一條數據*OK】");
}
if (handleContext.isSendNotify()) {
//發送消息,審批撤銷
}
}
}
4.監聽器
private Map<String, String> handlers = new HashMap<String, String>() {{
put("TASK_CREATED", "taskCreatedListener");
put("TASK_ASSIGNED", "taskAssignedListener");
put("PROCESS_COMPLETED", "processCompleteListener");
put("PROCESS_STARTED", "processStartedListener");
put("ACTIVITY_STARTED", "activityStartedListener");
put("ACTIVITY_COMPLETED", "activityCompletedListener");
put("ACTIVITY_SIGNALED", "activitySignaledListener");
put("HISTORIC_PROCESS_INSTANCE_ENDED", "historicProcessInstanceEnded");
put("ENTITY_DELETED", "entityDeletedListener");
}};
EventHandler
package com.cxx.soa.audit.activiti.listener.global;
import org.activiti.engine.delegate.event.ActivitiEvent;
public interface EventHandler {
//事件處理器
void handle(ActivitiEvent event);
}
分別實現EventHandler接口。
package com.cxx.soa.audit.activiti.listener.global;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class TaskCreatedListener implements EventHandler {
@Resource
private IEventSendService eventSendService;
@Override
public void handle(ActivitiEvent event) {
EventMsg eventMsg = EventCvtUtils.cvtMsg(event);
eventSendService.sendEventMessage(eventMsg);
}
}
GlobalEventListener
package com.cxx.soa.audit.activiti.listener.global;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class GlobalEventListener implements ActivitiEventListener, ApplicationContextAware {
private Map<String, String> handlers = new HashMap<String, String>() {{
put("TASK_CREATED", "taskCreatedListener");
put("TASK_ASSIGNED", "taskAssignedListener");
put("PROCESS_COMPLETED", "processCompleteListener");
put("PROCESS_STARTED", "processStartedListener");
put("ACTIVITY_STARTED", "activityStartedListener");
put("ACTIVITY_COMPLETED", "activityCompletedListener");
put("ACTIVITY_SIGNALED", "activitySignaledListener");
put("HISTORIC_PROCESS_INSTANCE_ENDED", "historicProcessInstanceEnded");
put("ENTITY_DELETED", "entityDeletedListener");
}};
@Override
public void onEvent(ActivitiEvent event) {
if (event.getType() != null) {
String eventType = event.getType().name();
logger.info("全局監聽器被調用 event type is ==>" + eventType);
String handlerKey = handlers.get(eventType);
EventHandler handler = getHandler(handlerKey);
if (handler != null) {
handler.handle(event);
}
}
}
private EventHandler getHandler(String handlerKey) {
if (handlerKey == null) {
return null;
}
return applicationContext.getBean(handlerKey, EventHandler.class);
}
@Override
public boolean isFailOnException() {
return false;
}
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
5.rocketMq消息通知
activiti的流程實例節點改變會被監聽器監聽,這裏可以處理我們的流程改變發送消息通知業務。
6.NativiteQuery
NativeObject
package com.cxx.soa.audit.activiti.nativequery;
import java.util.HashMap;
import java.util.Map;
public class NativeObject {
private String sql = "";
private Map<String, Object> parameters = new HashMap<>();
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Map<String, Object> getParameters() {
return parameters;
}
public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}
}
SqlUtil
package com.cxx.soa.audit.activiti.nativequery;
import com.alibaba.dubbo.common.utils.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SqlUtil {
/**
* 獲取for循環的sql組成
*/
public static <T> NativeObject forLoopUtil(String keyName, List<T> list) {
if (CollectionUtils.isEmpty(list)) {
return new NativeObject();
}
NativeObject nativeObject = new NativeObject();
Map<String, Object> param = new HashMap<>();
StringBuilder sql = new StringBuilder();
sql.append(" and ").append(keyName).append(" in (");
for (int i = 0; i < list.size(); i++) {
String key = keyName + i;
sql.append("#{").append(key).append("}");
param.put(key, list.get(i));
if (i != list.size() - 1) {
sql.append(",");
}
}
sql.append(")");
nativeObject.setSql(sql.toString());
nativeObject.setParameters(param);
return nativeObject;
}
}
1.HistoricTaskNativeSql
2.HistoricProcessInstanceNativeSql
3.RunTaskNativeSql
舉一個例子:
package com.dfire.soa.audit.activiti.nativequery;
import com.dfire.soa.audit.activiti.nativequery.query.HistoricTaskQuery;
import com.dfire.soa.audit.activiti.nativequery.query.RunTaskQuery;
import com.dfire.soa.audit.constants.activiti.UserTaskCategoryEnum;
import com.dfire.soa.audit.util.PageUtil;
import org.activiti.engine.HistoryService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.NativeHistoricTaskInstanceQuery;
import org.activiti.engine.task.NativeTaskQuery;
import org.activiti.engine.task.Task;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class RunTaskNativeSql {
@Resource
private TaskService taskService;
String FIELD = "select * from";
/**
* SELECT * FROM (select distinct *
from ACT_RU_TASK RES
WHERE RES.ASSIGNEE_ = '9993529366bf5cc80166c48da86a0004'
and RES.CATEGORY_ = 'approver'
and RES.TENANT_ID_ = 'ENTITY|USER:99935293'
union all
select distinct *
from ACT_RU_TASK RES
WHERE RES.ASSIGNEE_ = '9993529362901acf0162a8215bb701d0'
and RES.CATEGORY_ = 'ranker'
and RES.TENANT_ID_ = 'ENTITY|USER:99935293') AS T
order by T.CREATE_TIME_ DESC
limit 1,10;
* /
/**
* 按條件查詢
*/
public List<Task> queryRunTaskByUserIdAndGroupId(RunTaskQuery runTaskQuery) {
String tenantId = runTaskQuery.getTenantId();
String userId = runTaskQuery.getUserId();
String groupId = runTaskQuery.getGroupId();
int page = runTaskQuery.getPage();
int pageSize = runTaskQuery.getPageSize();
StringBuilder sql = new StringBuilder();
NativeTaskQuery query = taskService.createNativeTaskQuery();
sql.append(FIELD+"(").append(FIELD).append(" ACT_RU_TASK").append(" where TENANT_ID_=#{tenantId} AND CATEGORY_=#{category1} ");
query.parameter("category1", UserTaskCategoryEnum.APPROVER.getCode());
query.parameter("tenantId", tenantId);
if (userId != null) {
sql.append(" and ASSIGNEE_ =#{userId} ");
query.parameter("userId", userId);
}
sql.append(" union all ").append(FIELD).append(" ACT_RU_TASK").append(" where TENANT_ID_=#{tenantId} AND CATEGORY_=#{category2} ");
query.parameter("category2", UserTaskCategoryEnum.GROUP.getCode());
query.parameter("tenantId", tenantId);
if (groupId != null) {
sql.append(" and ASSIGNEE_ =#{groupId} ");
query.parameter("groupId", groupId);
}
sql.append(") AS T");
sql.append(" order by T.CREATE_TIME_ desc");
return query.sql(sql.toString()).listPage(PageUtil.getOffSet(page, pageSize), pageSize);
}
}
7.procdef流程定義
DeployManager 部署流程
package com.dfire.soa.audit.activiti.procdef.deploy;
import com.cxx.soa.audit.activiti.procdef.ProcDefRedisConstant;
import com.cxx.soa.audit.activiti.procdef.util.DynamicProcdefUtil;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class DeployManager {
@Resource
private RepositoryService repositoryService;
@Resource
private ICacheService cacheService;
public String draw(ProcessDefDTO processDefDTO) {
BpmnModel bpmnModel = DynamicProcdefUtil.drawUserTaskModel(processDefDTO);
String key = processDefDTO.getProcDefKey();
String name = processDefDTO.getName();
String type = processDefDTO.getCategory();
String tenantId = processDefDTO.getTenantId();
ValidateRulesUtils.validate(key);
ValidateRulesUtils.validate(name);
ValidateRulesUtils.validate(tenantId);
if (key.contains(":")) {
throw new BizException("procDefKey can not contain [:]", new Throwable());
}
Deployment deploy = repositoryService.createDeployment().
tenantId(tenantId)
.name(name)
.category(type)
.key(key)
.addBpmnModel(name + ".bpmn", bpmnModel).deploy();
String redisKey = ProcDefRedisConstant.getUserKeyByDefKey(tenantId, key);
cacheService.del(redisKey);
return deploy.getId();
}
public String deployXML(String url, String tenantId, String key, String name) {
Deployment deploy = repositoryService.createDeployment().
tenantId(tenantId)
.name(name)
.key(key)
.addClasspathResource(url).deploy();
String redisKey = ProcDefRedisConstant.getUserKeyByDefKey(tenantId, key);
cacheService.del(redisKey);
return deploy.getId();
}
}
DynamicProcdefUtil 動態流程定義工具
用代碼動態生成下面xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="MALL_EAT" name="AAAA" isExecutable="true">
<startEvent id="startEvent"></startEvent>
<endEvent id="endEvent"></endEvent>
<userTask id="U-07093FFAE1A04B48A02FA8233F96A777" name="發起申請" activiti:category="starter"></userTask>
<sequenceFlow id="S-473D16422F714A6DA9ECBB729E2B6685" sourceRef="startEvent" targetRef="U-07093FFAE1A04B48A02FA8233F96A777"></sequenceFlow>
<userTask id="U-4678FCC76DD447D3AF6DB8FE49E4E96D" name="null審批" activiti:assignee="992269506658e2f7016673070d060039" activiti:category="approver"></userTask>
<sequenceFlow id="S-2439C5433DD644EE9B216AF3F17DB6C7" sourceRef="U-07093FFAE1A04B48A02FA8233F96A777" targetRef="U-4678FCC76DD447D3AF6DB8FE49E4E96D"></sequenceFlow>
<sequenceFlow id="S-FD716036E8484196B2A2D14B1BE9F7A0" sourceRef="U-4678FCC76DD447D3AF6DB8FE49E4E96D" targetRef="endEvent"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_MALL_SUPPLY">
<bpmndi:BPMNPlane bpmnElement="MALL_SUPPLY" id="BPMNPlane_MALL_SUPPLY"></bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
package com.dfire.soa.audit.activiti.procdef.util;
import com.dfire.soa.audit.dto.activiti.procdef.ConditionDTO;
import com.dfire.soa.audit.dto.activiti.procdef.ElementDTO;
import com.dfire.soa.audit.dto.activiti.procdef.ProcessDefDTO;
import com.dfire.soa.audit.dto.activiti.procdef.UserTaskDTO;
import com.twodfire.exception.BizException;
import com.twodfire.util.UuidUtil;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.springframework.util.CollectionUtils;
import java.util.List;
public class DynamicProcdefUtil {
private final static String START_EVENT_ID = "startEvent";
private final static String END_EVENT_ID = "endEvent";
/**
* 畫用戶任務model
*/
public static BpmnModel drawUserTaskModel(ProcessDefDTO processDefDTO) {
// 1. Build up the model from scratch
BpmnModel model = new BpmnModel();
Process process = new Process();
model.setTargetNamespace(processDefDTO.getCategory());
model.addProcess(process);
final String processId = processDefDTO.getProcDefKey();
final String processName = processDefDTO.getName();
process.setId(processId);
process.setName(processName);
// 開始節點
process.addFlowElement(createStartEvent());
// 結束節點
process.addFlowElement(createEndEvent());
// 具體畫圖
draw(process, START_EVENT_ID, null, processDefDTO.getElementDTO());
return model;
}
/**
* 具體畫操作
*/
private static void draw(Process process, String parentTaskId, ConditionDTO condition, ElementDTO elementDTO) {
// 畫當前節點
String userTaskId = getId("U");
String parentId = userTaskId;
process.addFlowElement(createTask(userTaskId, elementDTO));
// 畫到上一個節點的線
process.addFlowElement(createSequenceFlow(parentTaskId, userTaskId, condition));
// 獲取下一個節點
ElementDTO nextElement = elementDTO.getNextElement();
List<ConditionDTO> nextElementWithCondition = elementDTO.getNextElementWithCondition();
// 已經結束,畫到終點
if (nextElement == null && nextElementWithCondition == null) {
process.addFlowElement(createSequenceFlow(userTaskId, END_EVENT_ID, null));
return;
}
// 還有下一個節點,繼續遞歸
if (nextElement != null) {
draw(process, parentId, null, nextElement);
}
// 還有下一個節點(有條件的分支),繼續遞歸
if (!CollectionUtils.isEmpty(nextElementWithCondition)) {
// 畫排他網關
String gateWayId = getId("G");
process.addFlowElement(createExclusiveGateway(gateWayId));
parentId = gateWayId;
for (ConditionDTO conditionDTO : nextElementWithCondition) {
draw(process, parentId, conditionDTO, conditionDTO.getElementDTO());
}
}
}
/**
* 創建節點統一入口,根據elementDTO是什麼類型的,畫什麼類型的Task
*/
private static Task createTask(String id, ElementDTO elementDTO) {
if (elementDTO instanceof UserTaskDTO) {
UserTaskDTO userTaskDTO = (UserTaskDTO) elementDTO;
return createUserTask(id, userTaskDTO.getName(), userTaskDTO.getAssignUserId(), userTaskDTO.getCategory());
}
throw new BizException("尚未支持", new Throwable());
}
/*任務節點*/
private static UserTask createUserTask(String id, String name, String assignee, String category) {
UserTask userTask = new UserTask();
userTask.setName(name);
userTask.setId(id);
userTask.setAssignee(assignee);
userTask.setCategory(category);
return userTask;
}
/*連線*/
private static SequenceFlow createSequenceFlow(String from, String to, ConditionDTO conditionDTO) {
SequenceFlow flow = new SequenceFlow();
flow.setId(getId("S"));
flow.setSourceRef(from);
flow.setTargetRef(to);
flow.setName("");
if (conditionDTO != null) {
flow.setConditionExpression(conditionDTO.getCondition());
flow.setName(conditionDTO.getConditionName());
}
return flow;
}
/*排他網關*/
private static ExclusiveGateway createExclusiveGateway(String id) {
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
exclusiveGateway.setId(id);
return exclusiveGateway;
}
/*開始節點*/
private static StartEvent createStartEvent() {
StartEvent startEvent = new StartEvent();
startEvent.setId(START_EVENT_ID);
return startEvent;
}
/*結束節點*/
private static EndEvent createEndEvent() {
EndEvent endEvent = new EndEvent();
endEvent.setId(END_EVENT_ID);
return endEvent;
}
private static String getId(String begin) {
String uuid = UuidUtil.getUUID().toUpperCase();
return begin + "-" + uuid;
}
}
BpmnCvtUtil
package com.cxx.soa.audit.activiti.procdef.util;
import com.alibaba.fastjson.JSON;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.springframework.util.CollectionUtils;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* bpmn轉dto工具
*/
public class BpmnCvtUtil {
private final static Logger logger = LoggerFactory.getLogger(BpmnCvtUtil.class);
private final static String START_EVENT_ID = "startEvent";
private final static String END_EVENT_ID = "endEvent";
private static ExpressionFactory factory;
//EL表達式工具
static {
factory = new ExpressionFactoryImpl();
}
/**
* 獲取user流程定義
*/
public static UserTaskDTO getUserTasks(BpmnModel bpmnModel) {
UserTaskDTO starter = (UserTaskDTO) getNextElement(bpmnModel, START_EVENT_ID);
if (starter == null) {
logger.warn("first is null" + JSON.toJSONString(bpmnModel.getMainProcess().getId()));
return null;
}
UserTaskDTO current = starter;
while (true) {
UserTaskDTO nextElement = (UserTaskDTO) getNextElement(bpmnModel, current.getId());
if (nextElement == null) {
break;
}
current.setNextElement(nextElement);
current = nextElement;
}
return starter;
}
/**
* 獲取當前節點的下一個節點
*/
public static ElementDTO getNextElement(BpmnModel bpmnModel, String id) {
Process mainProcess = bpmnModel.getMainProcess();
Map<String, FlowElement> flowElementMap = mainProcess.getFlowElementMap();
// 獲取當前節點
FlowElement flowElement = flowElementMap.get(id);
if (flowElement instanceof FlowNode) {
List<SequenceFlow> outgoingFlows = ((FlowNode) flowElement).getOutgoingFlows();
if (!CollectionUtils.isEmpty(outgoingFlows)) {
SequenceFlow sequenceFlow = outgoingFlows.get(0);
FlowElement nextElement = flowElementMap.get(sequenceFlow.getTargetRef());
if (nextElement instanceof EndEvent) {
return null;
}
return cvtDto(nextElement);
}
} else {
logger.error("暫未支持 class:" + flowElement.getClass().getName());
return null;
}
return null;
}
private static ElementDTO cvtDto(FlowElement flowElement) {
if (flowElement == null) {
return null;
}
if (flowElement instanceof UserTask) {
UserTask userTask = (UserTask) flowElement;
UserTaskDTO userTaskDTO = new UserTaskDTO();
userTaskDTO.setName(userTask.getName());
userTaskDTO.setId(userTask.getId());
userTaskDTO.setCategory(userTask.getCategory());
userTaskDTO.setAssignUserId(userTask.getAssignee());
return userTaskDTO;
} else {
logger.error("暫未支持 class:" + flowElement.getClass().getName());
return null;
}
}
/**
* 替換掉變量
*
* @param expression
* @param variable
* @return
*/
public static String replaceVariable(String expression, Map<String, Object> variable) {
Set<Map.Entry<String, Object>> entries = variable.entrySet();
for (Map.Entry<String, Object> entry : entries) {
String name = entry.getKey();
Object value = entry.getValue();
SimpleContext context = new SimpleContext();
try {
context.setVariable(name, factory.createValueExpression(value, value.getClass()));
ValueExpression valueExpression = factory.createValueExpression(context, expression, String.class);
return (String) valueExpression.getValue(context);
} catch (Exception e) {
continue;
}
}
return "";
}
}
8.service 接口
9.工具類
測試用例
1.流程定義
import org.activiti.engine.RepositoryService;
import org.apache.commons.lang.time.StopWatch;
import org.junit.Test;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
public class ActivitiDefTest {
@Resource
private DeployManager deployManager;
@Resource
private RepositoryService repositoryService;
@Resource
private BpmnQueryManager bpmnQueryManager;
@Resource
private IActivitiProcdefService activitiProcdefService;
@Test
public void test03Save(){
List<DefaultUserTaskDTO> defaultUserTaskDTOs = new ArrayList<>();
DefaultUserTaskDTO defaultUserTaskDTO1 = new DefaultUserTaskDTO();
defaultUserTaskDTO1.setName("審批人1");
defaultUserTaskDTO1.setAssignUserId("9993529366bf5cc80a86a0004");
DefaultUserTaskDTO defaultUserTaskDTO3 = new DefaultUserTaskDTO();
defaultUserTaskDTO3.setName("職級");
defaultUserTaskDTO3.setCategory(UserTaskCategoryEnum.GROUP.getCode());
String roleId = "9993529362901a8215bb701d0";
defaultUserTaskDTO3.setAssignUserId(roleId);
DefaultUserTaskDTO defaultUserTaskDTO4 = new DefaultUserTaskDTO();
defaultUserTaskDTO4.setName("審批人3");
defaultUserTaskDTO4.setAssignUserId("99935293610166fc7d38570007");
defaultUserTaskDTOs.add(defaultUserTaskDTO1);
defaultUserTaskDTOs.add(defaultUserTaskDTO3);
defaultUserTaskDTOs.add(defaultUserTaskDTO4);
Result result = activitiProcdefService.saveDefaultUserTaskProcdef(TenantId.ENTITY_USER("99935293"), "MALL_BUNK", "鋪位111", defaultUserTaskDTOs);
}
@Test
public void testGet() {
Result<List<DefaultUserTaskDTO>> result = activitiProcdefService.getDefaultUserTaskProcdef(TenantId.ENTITY_USER("99226563"), "MALL_AGREEMENT");
System.out.println(result.getModel());
}
}
2.流程的發起
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.junit.Test;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ActivitiStartTest extends AuditBaseTest {
@Resource
private IActivitiHandleService handleService;
@Resource
private TaskService taskService;
@Resource
private RuntimeService runtimeService;
@Resource
private IActivitiCommentService activitiCommentService;
@Test
public void test1() {
HandleContext handleContext = new HandleContext();
// attachment
AttachmentDTO attachmentDTO = new ObjectAttachmentDTO("{\"name\":\"haha\"}");
handleContext.setAttachmentDTO(attachmentDTO);
// variable
Map<String, Object> variable = new HashMap<>();
variable.put("test", "test");
handleContext.setVariables(variable);
// comment
handleContext.setComment("我要發起審批了");
for (int i=0; i< 10; i++) {
Result<String> result = handleService.startProcessInstance("MALL_BK", TenantId.ENTITY_USER("99935293"), "8b3db0ffb8dd4add9ecbbf3391", "this is busssKey", "lxh名稱" + i, handleContext);
}
}
}
@Test
public void batchStartProcessInstance(){
HandleContext handleContext = new HandleContext();
List<StartObject> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
int finalI = i;
list.add(new StartObject() {{
setBusinessKey("cxx key");
setName("cxx 審批" + finalI);
setHandleContext(handleContext);
}});
}
handleService.batchStartProcessInstance("MALL_BUNK", TenantId.ENTITY_USER("99935293"), "2222", list);
}
}
3.流程操作測試
package com.dfire.soa.audit.activiti;
import com.alibaba.fastjson.JSON;
import org.activiti.engine.HistoryService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.task.Attachment;
import org.junit.Assert;
import org.junit.Test;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
public class ActivitiHandlerServiceTest extends AuditBaseTest {
@Resource
private IActivitiHandleService activitiHandleService;
@Resource
private HistoryService historyService;
@Resource
private TaskService taskService;
@Resource
private PersonalCommandManager personalCommandManager;
private HandleContext handleContext;
static String tenantId = "0001233";
static String userId = "123456";
static String taskId = "";
static String startUserId = "100000";
static String businessKey = "BUSINESS_KEY";
static String processInstanceId = "";
@Test
public void agreeTest() {
handleContext = getHandleContext();
taskId = "D3FF88E5E50011E1102000A011942";
tenantId = "99935293";
userId = "9993529366bf5cc80166c48da86a0004";
Result agree = activitiHandleService.agree(TenantId.ENTITY_USER(tenantId), userId, taskId, handleContext);
String deleteReason = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult().getDeleteReason();
String category = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult().getCategory();
System.out.println("category****");
//改變節點用戶ID
personalCommandManager.updateHiTaskAssigneeAndcategory(taskId, "123456789", UserTaskCategoryEnum.APPROVER.getCode());
System.out.println("修改成功");
Assert.assertTrue(agree.isSuccess());
Assert.assertTrue(deleteReason.equals(StatusEnum.PASS.getCode()));
}
@Test
public void disAgreeTest() {
handleContext = getHandleContext();
handleContext.setComment("拒絕理由:情況不屬實!");
taskId = "05D35D01AD1011E8B82420A80C808";
Result result = activitiHandleService.disAgree(TenantId.ENTITY_USER(tenantId), userId, taskId, handleContext);
String deleteReason = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult().getDeleteReason();
int size = taskService.getTaskComments(taskId).size();
Assert.assertTrue(result.isSuccess());
Assert.assertTrue(deleteReason.equals(StatusEnum.UN_PASS.getCode()));
Assert.assertTrue(size > 0);
}
@Test
public void cancelTest() {
handleContext = getHandleContext();
processInstanceId = "127D925BADCE8A1BB02420A80C809";
startUserId = "111";
Result result = activitiHandleService.cancel(TenantId.ENTITY_USER(tenantId), startUserId, processInstanceId, handleContext);
HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
String deleteReason = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getDeleteReason();
Assert.assertTrue(result.isSuccess());
Assert.assertNull(historicTaskInstance);
Assert.assertTrue(deleteReason.equals(StatusEnum.CANCEL.getCode()));
}
@Test
public void cancelByBusinessKeyTest() {
Result result = activitiHandleService.cancelByBusinessKey(TenantId.ENTITY_USER("99226561"), "acb817e354d043679ba0026a618c", "MALL_METER_BILL", "369971472288055296", handleContext);
Assert.assertTrue(result.isSuccess());
}
}
System.out.println(out);
System.out.println(out.toString().getBytes().length);
}
private HandleContext getHandleContext() {
return new HandleContext();
}
}