Activiti工作流-實戰篇(和spring整合)

在這裏插入圖片描述

參考資料: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();
    }
}

希望對有需要的人能有用…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章