Java工作流管理系统(activity6.0)

activity6.0工作流系统知识点文章

第一章 activity流程部署(自动部署与动态BPMN部署)
第二章 activity变量使用
第三章 activity权限控制(代办任务查询)
第四章 activity审核任务(签领、完成任务、跳过节点、新增节点、设置委托人)
第五章 activity节点任意跳转(包含多实例)
第六章 activity新增节点(包含多实例)
第七章 activity多实例节点
第八章 activity流程监听器在项目中使用
第九章 activity自定义增删查改



前言

提示:流程设计器相对比较专业,客户一般不会用,也使得程序复杂化,加大维护成本。通过业务系统实现配置节点,动态构建流程BPMN文件来代替流程设计器,操作简单易用。

	工作流只是协助进行业务流程管理,对业务系统的业务流程进行自动化管理。没有工作流管理系统对业务系统影响不大,只不过有了工作流可以更好的管理业务流程,提高系统的可扩展性。
	作为流程管理,工作流可以独立部署为独立程序,通过接口与业务系统进行对接。
	下面我分两部分进行分析,一个是工作流管理系统,另一个是业务系统对接工作流功能(也可称为工作流客户端)。

工作流系统核心功能:
1、动态创建bpmn,并创建流程实例
2、查询代办任务
3、审核任务(签领、任意跳过节点、新增节点、完成任务)
4、历史流程数据查询

业务系统对接工作流功能:
1、定义业务流程,配置流程节点
2、审核流程任务(查询代办任务,完成流程任务)
3、监控流程(对在运行中的流程终止、新增任务、跳过任务等)

程序实现方式:
为了简化流程,对工作流制定规则如下:
1、动态创建bpmn固定为:开始节点—用户节点—排它网关—满足下一个用户节点或不满足结束节点。如图:
在这里插入图片描述

备注:如果需要控制特殊流向,可通过任意跳转、新增节点等来实现。

2、用户节点关键属性(审核人、候选人、候选组),设置为全局参数,来实现用户数据权限。
2.1第一个节点为发起人,流程启动时自动完成(谁发起就作为发起人)。
2.1候选组(组内人员都看到,其中一个审核通过即完成)
2.3候选人只有一个审核人时为单实例节点,候选人有多个审核人为多实例节点
备注:不采用activity提供的用户与组功能,减少系统复杂度。

举个例子:
报销单据审批流程,配置如下:发起人–》经费来源部门–》领导审核

要求:“经费来源部门”节点,需要经费大于10万才需要经过它审核

  1. 当金额大于10万,传给工作流节点为:
    发起人–》经费来源部门–》领导审核
  2. 当金额小于等于10万,传给工作流节点为:
    发起人–》领导审核

在创建流程实例前确认金额,工作流客户端确认好流程节点,就完成基本流程实例。
如果流程已发起,可通过流程监控对现有的流程进行终止、新增节点、跳过节点等等。

那如果创建流程前不知道,该如何做呢?
可以通过任意跳过节点、新增节点等功能控制代码,实现特殊流程流向;
还不满足的,你还可以通过流程设计器进行设置,把bpmn放在resources\processes路径下即可,然后特殊处理(特殊情况,自行扩展)。


一、工作流管理系统

工作流管理系统是独立部署,通过接口为业务系统提供api。如果业务系统也为Java,可以考虑采用RMI,目前工作流管理系统就是采用rmi接口。

系统采用SpringBoot2.3.4+activity6.0,源码结构图如下:
在这里插入图片描述
备注:具体功能,有兴趣可以通过下载源码进行研究

1.部分源码功能分析

1、动态创建bpmn,并创建流程实例
// 创建bpmn文件并部署流程
public class BuildFlowDeploymentService {

    @Resource private RepositoryService repositoryService;
    @Resource private RuntimeService runtimeService;
    
    private static final Map<String, List<ProcessTask>> processKeyCache = Maps.newHashMap();
public String createFlowDeployment(StartProcessInput input) {

        log.info("开始动态创建部署流程...input = {}", input);
        String processId = input.getProcessDefineKey();
        if (processKeyCache.containsKey(processId)) {
            //如果配置节点一样,采用缓存配置文件,避免重复创建
            Set<ProcessTask> processTasks = Sets.newHashSet(processKeyCache.get(processId));
            Set<ProcessTask> newProcessTasks = Sets.newHashSet(input.getProcessTasks());
            Set<ProcessTask> diff = Sets.symmetricDifference(processTasks, newProcessTasks);
            if (CollectionUtils.isEmpty(diff)) return processId;

            processKeyCache.remove(processId);
        }
        // 1. 建立模型
        BpmnModel model = new BpmnModel();
        Process process = new Process();
        List<ActivitiListener> executionListeners = Lists.newArrayList(
            addTaskListener("start"),
            addTaskListener("end")
        );
        process.setExecutionListeners(executionListeners);
        model.addProcess(process);
        process.setId(processId);
        process.setName(input.getProcessName());
        process.setDocumentation(input.getProcessName());
        //拼接节点信息
        createFlowElement(process, input.getProcessTasks());

        //部署流程
        repositoryService.createDeployment()
            .addBpmnModel(process.getId() + FILE_SUFFIX, model)
            .name(process.getId() + "_deployment")
            .deploy();

//        createProcessBpmn(model, processId); 尽在测试时打印查看效果图使用,正式用时需要注释掉
        processKeyCache.put(processId, input.getProcessTasks());
        return processId;
    }

    // 流程启动,创建流程实例
    public String startFlow(StartProcessInput input) {
        Map<String, Object> vars = Maps.newHashMap();
        vars.putAll(input.getConditionExpression());//添加条件参数
        vars.put(VARS_TITLE, input.getTitle());
        vars.put(VARS_SEND_UNIT_NAME, input.getSendUnitName());
        vars.put(VARS_SEND_USER_NAME, input.getSendUserName());
        vars.put(VARS_TASK_UNIT_ID, input.getTaskUnitId());
        vars.put(VARS_USER_TASK_NAME, input.getUserTaskName());
        vars.put(VARS_PROCESS_DEFINITION_ID, input.getProcessDefinitionId());
        vars.put(VARS_SECONDARY_UNIT_ID, input.getSecondaryUnitId());
        vars.put(VARS_REMARK, input.getRemark());

        // ...自定义表单数据
        input.getCustomForm().forEach((key, val) -> vars.put(VARS_PREFIX_FORM_PROPERTY.concat(key), val));

        String businessId = input.getBusinessId() + SPLIT_COMMA + input.getSendId() + SPLIT_COMMA + input.getProcessDefinitionId();
        log.debug("businessId->[{}]", businessId);
        String processInstanceId = startProcessInstance(input.getAssignee(), input.getProcessDefineKey(),
            input.getClassPrefix(), businessId, vars);
        log.info("流程已启动, 入参信息[{}], 流程id[{}]", input, processInstanceId);

        setProcessName(processInstanceId, input.getProcessName());

        List<Task> tasks = findTaskByProcInsId(processInstanceId);
        if (CollectionUtils.isEmpty(tasks)) {
            log.error("流程配置不符合规则,设置第一个节点发起人");
            BusinessException.throwConstraintException("流程配置不符合规则,设置第一个节点发起人");
        }
        Task task = tasks.get(0);// 默认第一个节点发起人,永远存在值

        completeAutoClaimTask(task.getId(), input.getAssignee(), processInstanceId, input.getComment(), vars);
        log.info("任务[{}]已完成", task);

        return processInstanceId;
    }

备注:如果想了解更多内容,可查看《第一章 activity流程部署(自动部署与动态BPMN部署)》

2、查询代办任务
 /**
     * 获取待办列表
     */
    public TodoTaskSearchOutput todoTaskSearch(TodoTaskSearchInput input) {

        TaskQuery taskQuery;
        //CandidateGroups、CandidateUser同时为空时,返回全部数据(流程监控)
        if (CollectionUtils.isEmpty(input.getCandidateGroups()) && Strings.isNullOrEmpty(input.getCandidateUser())) {
            taskQuery = taskService.createTaskQuery()
                .includeProcessVariables().active()
                .orderByTaskCreateTime();
        } else {
            if (CollectionUtils.isEmpty(input.getCandidateGroups()))
                throw BusinessException.withMessage(ErrorCode.ERR_10001, "角色不能为空");

            taskQuery = taskService.createTaskQuery()
//                .taskCandidateUser(input.getCandidateUser())
                .taskCandidateOrAssigned(input.getCandidateUser())
                .taskCandidateGroupIn(input.getCandidateGroups())//支持多个角色
                .includeProcessVariables().active()
                .orderByTaskCreateTime();
        }

        // 设置查询条件
        taskQuery(taskQuery, input);

        long total = taskQuery.count();
        int page = Math.max(input.getPager().getPage() - 1, 0);
        List<Task> toClaimList = taskQuery.listPage(page * input.getPager().getSize(), input.getPager().getSize());//分页查询

备注:如果想了解更多内容,可查看《activity权限控制(代办任务查询)》

3、审核任务(签领、任意跳过节点、新增节点、完成任务)
@Transactional
    public AuditTaskEndSearchOutput taskAudit(TaskAuditInput input) {
        Task task = workFlowService.getTask(input);
        if (task == null) throw BusinessException.with(ErrorCode.ERR_40103);

        log.info("任务[{}]已完成", task.getId());
        Map<String, Object> vars = taskAuditBuildVars(input);

        // 流程自带表单数据
        log.debug("流程自带表单数据 count = {}", input.getCustomData().size());
        input.getCustomData().forEach((k, v) -> vars.put(VARS_PREFIX_FORM_PROPERTY + k, v));

        log.info("开始为用户[{}]自动签领任务[{}]", input.getAssignee(), task.getId());
        workFlowService.claim(task.getId(), input.getAssignee());
        workFlowService.complete(task.getId(), input, vars);
        // 1、当审核不通过时,流程结束
        if (0 == input.getIsPass()) {
            taskNotPassService.activityCompleted(input.getId());
            return findAuditTaskEndSearchOutput(input);
        }

        // 2、下一个节点被删除或新增节点处理
        workFlowService.addOrJumpProcessTask(input);

        // 3、设置下一个节点委托人
        if (!Strings.isNullOrEmpty(input.getEntrustedAgent())) {
            UpdateTaskAgentInput agentInput = new UpdateTaskAgentInput();
            agentInput.setProcessInstanceId(input.getId());
            agentInput.setEntrustedAgent(input.getEntrustedAgent());
            agentInput.setOriginalHandler(input.getOriginalHandler());
            workFlowService.updateTaskAgent(agentInput);
        }

        return findAuditTaskEndSearchOutput(input);
    }

备注:如果想了解更多内容,可查看《activity审核任务(签领、任意跳过节点、新增节点、设置委托人、完成任务)》

4、历史流程数据查询
public FindHistoricTaskOutput findHistoricTask(ProcInsIdInput input) {
        FindHistoricTaskOutput result = new FindHistoricTaskOutput();
        List<HistoricTaskInstance> hisTasks = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(input.getId())
            .includeProcessVariables()
            .includeTaskLocalVariables()
            .orderByHistoricTaskInstanceStartTime()
            .asc()
            .list();

        if (CollectionUtils.isEmpty(hisTasks)) return result;
// 返回历史数据内容,这里省略
5、激活或挂起

代码如下(示例):

// 激活
runtimeService.activateProcessInstanceById(processId);

// 挂起
runtimeService.suspendProcessInstanceById(processId);
6、修改节点办理人(代办)

代码如下(示例):

public void updateTaskAgent(UpdateTaskAgentInput input) {
        if (Strings.isNullOrEmpty(input.getEntrustedAgent())) return;

        Task task = taskService.createTaskQuery().processInstanceId(input.getProcessInstanceId()).singleResult();
        if (task == null) {
            log.debug("流程已结束,不需要再设置委托人");
            return;
        }

        log.debug("设置负责人或委托人");
        taskService.setAssignee(task.getId(), input.getEntrustedAgent());//委托人
        if (!Strings.isNullOrEmpty(input.getOriginalHandler()))
            taskService.setOwner(task.getId(), input.getOriginalHandler());
    }

2.提供的接口方式

1、RMI接口;
2、webservice服务接口

接口服务端RMI配置:

@Slf4j
@Configuration
public class RmiConfiguration implements InitializingBean {

    @Value("${app.rmi.registryPort}")
    private int registryPort;

    @Value("${app.rmi.servicePort}")
    private int servicePort;

    @Resource private IWorkflowEndPoint workflowPoint;

    @Bean
    public RmiServiceExporter workflowPointExporter() {
        return export(IWorkflowEndPoint.class, workflowPoint, "flow");
    }

    private RmiServiceExporter export(Class<?> serviceInterface, Object instance, String instanceName) {
        RmiServiceExporter exporter = new RmiServiceExporter();

        exporter.setServiceInterface(serviceInterface);
        exporter.setService(instance);
        exporter.setServiceName(instanceName);
        exporter.setRegistryPort(registryPort); //注册端口
        exporter.setServicePort(servicePort); //通讯端口

        return exporter;
    }

    @Override
    public void afterPropertiesSet() {
        log.info("--------------RMI Server started@RegistryPort[{}], ServicePort[{}] ---------------------", registryPort, servicePort);
    }
}

以下提供接口类:

public interface IWorkflowEndPoint {

    /**
     * 启动流程
     */
    ApiResult<String> startProcess(RmiCommand command);

    /**
     * 根据流程实例id删除流程实例
     */
    ApiResult<String> deleteProcessInstance(RmiCommand command);

    /**
     * 开始审核任务(完成任务节点)
     */
    ApiResult<String> taskAudit(RmiCommand command);

    /**
     * 判断流程是否即将结束(当前任务完成后结束)
     */
    ApiResult<String> isProcessEnd(RmiCommand command);

    /**
     * 根据流程id,返回当前流程实例的历史数据
     */
    ApiResult<String> findHistoricTask(RmiCommand command);

    /**
     * 根据业务id,返回所有历史数据并根据实例进行分组
     */
    ApiResult<String> findMergeHisProcess(RmiCommand command);

    /**
     * 获取待办数量
     */
    ApiResult<String> todoTaskTotal(RmiCommand command);

    /**
     * 待办列表左侧的快捷查询标签
     */
    ApiResult<String> todoTaskQueryTags(RmiCommand command);

    /**
     * 获取待办列表
     */
    ApiResult<String> todoTaskSearch(RmiCommand command);

    /**
     * 获取待办任务信息(审核人、候选组、单位等等)
     */
    ApiResult<String> todoTaskInfo(RmiCommand command);

    /**
     * 获取已办--经办(曾经审核过)
     */
    ApiResult<String> auditTaskSearch(RmiCommand command);

    /**
     * 发件(没有办理结束)
     */
    ApiResult<String> processSendSearch(RmiCommand command);

    /**
     * 办结(办理结束)
     */
    ApiResult<String> processEndSearch(RmiCommand command);

    /**
     * 办理激活或挂起
     */
    ApiResult<String> activeOrSuspendProcess(RmiCommand command);

    /**
     * 修改节点办理人
     */
    ApiResult<String> updateTaskAgent(RmiCommand command);

    /**
     * 添加节点为代办任务
     */
    ApiResult<String> addUserTasks(RmiCommand command);

    /**
     * 自由跳转节点(跳过代办任务时需要)
     */
    ApiResult<String> freeNodeJump(RmiCommand command);
}

2.业务系统对接工作流功能(工作流客户端)

提供截图以及备注,直观了解业务系统与工作流对接功能,这里只是简单介绍能做什么,重点在讲解工作流系统功能。

1.流程节点配置

在这里插入图片描述

备注:动态节点类型,用于程序控制该节点是否保留

2.流程待办任务与审核页面

首页可放一个提醒是否有代办任务
在这里插入图片描述
进入代办任务页面:
在这里插入图片描述
点击办理
在这里插入图片描述

3.流程监控(终止流程、跳过、增减节点、修改审批人等)

查看所有的待办任务,可以终止流程,提醒审核人等;
在这里插入图片描述
进入流程督办,可以对流程节点进行跳过、增减节点、修改审批人等
在这里插入图片描述

三、测试用例

发起流程->查询待办任务->审核(新增节点、跳过节点、多实例参数赋值)->获取历史流程信息

代码如下(示例):

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("localdev")
public class WorkFlowTestService{
    @Resource private WorkFlowApp workFlowApp;

    @Test
    public void setMngWorkflowService() {
        StartProcessInput input = new StartProcessInput();
        input.setProcessDefinitionId("leaveId");
        input.setProcessDefineKey("WF0007");
        input.setProcessName("测试请假流程");
        // 流程节点配置
        input.setProcessTasks(findTasks());
        input.setAssignee("ceshi");
        // 业务id
        input.setBusinessId("businessId");
        // 业务需要,这个业务实现类前缀
        input.setClassPrefix("leave");
        // 单位,用于数据权限的控制,现在在哪个单位中
        input.setTaskUnitId(VARS_CURRENCY_UNIT_VALUE);
        input.setUserTaskName("节点1");
        input.setTitle("标题");
        input.setComment("备注");

        // 如果第二个节点为多实例时,需要传值
        Map<String, Object> map = Maps.newHashMap();
        List<String> assignList = Lists.newArrayList("multiUser1", "multiUser2", "multiUser3");
        map.put("assignList", assignList);
        map.put("loopCardinality", assignList.size());
        input.setConditionExpression(map);

        StartProcessOutput output = workFlowApp.startProcess(input);
        log.error("返回对象:{}", output);
    }

    /**
     * 从当前的待办任务直接跳转到多实例节点
     */
    @Test
    public void jump_multi_test() {
        FreeNodeJumpInput input = new FreeNodeJumpInput();
        input.setProcessInstanceId("7");
        input.setTargetNodeId("taskId3");

        Map<String, Object> vars = Maps.newHashMap();
        vars.put("taskUnitId", "currencyUnitValue");
        List<String> assignList = Lists.newArrayList("addMultiUser1", "addMultiUser2");
        vars.put("assignList", assignList);
        vars.put("loopCardinality", assignList.size());
        input.setVars(vars);

        FreeNodeJumpOutput taskOutput = workFlowApp.freeNodeJump(input);
        System.out.println(taskOutput.getSendUserId());
    }

    // 历史数据查询
    @Test
    public void his_test() {
        String id = "7";
        ProcInsIdInput insIdInput = new ProcInsIdInput();
        insIdInput.setId(id);
        FindHistoricTaskOutput taskOutput = workFlowApp.findHistoricTask(insIdInput);
        System.out.println(taskOutput.getItems());
    }

    //执行任务
    @Test
    public void taskAudit_test() {
        TaskAuditInput input = new TaskAuditInput();
        input.setId("7");
        input.setIsPass(1);
        input.setAssignee("multiUser2");
        input.setIsUserForCurrentTask(true);
//        input.setCandidateGroups(Lists.newArrayList("startRole"));
        input.setTaskUnitId("currencyUnitValue");
        input.setHandleUnitName("节点multiUser2单位");
        input.setHandleUserName("节点multiUser2有用户");
        input.setNexTaskUnitId("currencyUnitValue");


        // 如果下一个节点为新增节点,需要设置下一个节点配置,setTaskModelList这里就是配置
//        input.setNexTaskName("新增节点");
//        input.setLastNodeId("taskId3");
//        input.setTaskModelList(findAddTasks());
//        input.setNextCandidateUser("mUser1,mUser2");
//        input.setNextCandidateGroups(Lists.newArrayList("roleEnd"));

        // 如果下一个节点为多实例节点
//        Map<String, Object> map = Maps.newHashMap();
//        List<String> assignList = Lists.newArrayList("multiUser1", "multiUser2", "multiUser3");
//        map.put("assignList", assignList);
//        map.put("loopCardinality", assignList.size());
//        input.setConditionExpression(map);
        AuditTaskEndSearchOutput output = workFlowApp.taskAudit(input);

        log.error("返回参数[{}]", output);
    }

    @Test
    public void isProcessEnd_test() {
        TaskAuditInput input = new TaskAuditInput();
        input.setId("7");
        input.setIsPass(1);
        input.setAssignee("multiUser3");
        input.setIsUserForCurrentTask(true);
//        input.setCandidateGroups(Lists.newArrayList("startRole"));
        input.setTaskUnitId("currencyUnitValue");
        input.setHandleUnitName("节点multiUser3单位");
        input.setHandleUserName("节点multiUser3有用户");

        input.setNexTaskUnitId("currencyUnitValue");
        input.setNexTaskName("节点2");
        ProcessEndOutput output = workFlowApp.isProcessEnd(input);
        log.info("节点是否完成:" + (output.getIsTaskCompleted() ? "是" : "否"));
        log.info("流程是否结束:" + (output.getIsProcessEnd() ? "是" : "否"));
    }


    //获取待办任务
    @Test
    public void todoTaskSearch_test() {
        TodoTaskSearchInput input = new TodoTaskSearchInput();
        input.setProcInsId("7");
//        input.setCandidateUser("user1");
//        input.setCandidateGroups(Lists.newArrayList("startRole"));
        TodoTaskSearchOutput output = workFlowApp.todoTaskSearch(input);
        log.info("返回参数[{}]", output.getItems());
    }

    /**
     * 测试数据
     */
    private List<ProcessTask> findTasks() {
        List<ProcessTask> tasks = Lists.newArrayList();

        // 第一个(i = 1)节点固定为发起人
        for (int i = 1; i <= 4; i++) {
            ProcessTask task = new ProcessTask();
            task.setTaskId("taskId" + i);

            if (i == 2) {
                // 流程发起就确认通过率、并行等条件
                task.setMultiInstanceTask(true);
                MultiInstanceInput multiTask = new MultiInstanceInput();
                multiTask.setCompletionRatio(BigDecimal.valueOf(0.51));
                multiTask.setSequential(true);
                task.setMultiTask(multiTask);
            }

            if (i == 3) {
                task.setCandidateUser("2021001");
            }

            // 不是多实例与候选人,那默认就是根据组判断权限

            tasks.add(task);
        }

        return tasks;
    }


    private List<ProcessTask> findAddTasks() {
        List<ProcessTask> tasks = Lists.newArrayList();
        ProcessTask task = new ProcessTask();
        task.setTaskId("addTaskId1");

        // 流程发起就确认通过率、并行等条件
        task.setMultiInstanceTask(true);
        MultiInstanceInput multiTask = new MultiInstanceInput();
        multiTask.setCompletionRatio(BigDecimal.valueOf(0.51));

        multiTask.setSequential(false);
        task.setMultiTask(multiTask);
        tasks.add(task);

        return tasks;
    }

}

参考资料

中文操作文档参考地址:
https://blog.csdn.net/Nought_Love/article/details/100081893

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值