第12天-审批中心

本文详细介绍了审批中心的查询与修改流程功能的实现,包括根据ID查询审批流程数据并回显,以及如何处理数据转换异常。同时,探讨了动态构建Bpmn模型的方法,通过Activiti API创建并部署审批流程到工作流引擎。此外,还讨论了移动端审批的三大功能:发起审批、我发起的和我审批的,强调了服务端接口在审批流程中的重要角色。
摘要由CSDN通过智能技术生成

第12天-审批中心

1. 审批管理

1.1 需求

1). 根据ID查询流程

当点击列表页面中的 表单设计、流程编辑、审批设置 按钮,需要根据当前审批流程的ID查询流程数据,并进行页面数据回显。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02qG5WTT-1650203805194)(img/image-20210302174020995.png)]

2). 修改流程数据

当对流程数据编辑完毕之后,点击 “发布” 按钮,将当前流程数据更新到数据库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXvZAZ3R-1650203805195)(img/image-20210302174224117.png)]

1.2 根据ID查询流程

1.2.1 思路分析

根据ID查询审批流程,查询出来的数据包含三个部分,审批设置、表单设计、流程设计。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VfidZaxE-1650203805196)(img/image-20210301114451471.png)]

服务端封装的数据模型,应该也包含三个部分:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ApproveDefinitionResultDto implements Serializable {
    private ApproveDefinitionBaseDataDto baseData; //基础信息
    private List<ApproveDefinitionTableDataResultDto> tableData; //表单信息
    private String flowData; //流程信息
}
/**
 * 审批定义基础信息
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApproveDefinitionBaseDataDto implements Serializable {
    
    private String id;//ID
    private String groupType; // 审批类型
    private String name; // 审批名称
    private List<AllowUserObjDto> allowUserJson; // 谁可以发起这个审批json
    private String description; // 审批说明
    private String icon; // 图标
    private String opinionPrompt; // 审批意见填写提示
    private String opinionRequired; // 审批意见是否必填
    private Integer seq; // 序号
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApproveDefinitionTableDataResultDto {
    //字段主键
    private String fieldKey;
    //组件名称
    private String lab;
    //组件类型
    private String type;
    //组件图标
    private String icon;
    //标题
    private String title;
    //组件数据设置
    @JsonDeserialize(using = DataDeserializer.class)
    private Map<String,Object> data;
    //是否源于模板
    private String fromTemplate = "0";
}
1.2.2 接口定义
@ApiOperation(value = "流程定义: 根据ID查询")
public Result<ApproveDefinitionResultDto> queryById(String id);
1.2.3 代码实现

1). ApproveDefinitionController

@Override
@GetMapping("/approveDefinition/{id}")
public Result<ApproveDefinitionResultDto> queryById(@PathVariable("id") String id){
    ApproveDefinitionResultDto approveDefinitionDto = approveDefinitionService.queryById(id);
    return Result.success(approveDefinitionDto);
}

2). ApproveDefinitionService

/**
  * 根据ID查询流程定义信息
  * @param id
  * @return
  */
public ApproveDefinitionResultDto queryById(String id);

3). ApproveDefinitionServiceImpl

@Override
public ApproveDefinitionResultDto queryById(String id) {
    ApproveDefinitionResultDto approveDefinition = new ApproveDefinitionResultDto();
	
    //1. 查询流程定义基础信息
    ApproveDefinition definition = approveDefinitionMapper.selectById(id);
    ApproveDefinitionBaseDataDto definitionBaseDataDto = BeanHelper.copyProperties(definition, ApproveDefinitionBaseDataDto.class, "allowUserJson");

    //组装allowUserJson
    List<AllowUserObjDto> allowUsers = JsonUtils.toList(definition.getAllowUserJson(), AllowUserObjDto.class);
    definitionBaseDataDto.setAllowUserJson(allowUsers);

    //2. 组装表单(客户端控件)信息
    List<ApproveDefinitionTableDataResultDto> tableList = null ;
    String formJson = definition.getFormJson();
    if(StrUtil.isNotEmpty(formJson)){
        tableList = JsonUtils.toList(formJson, ApproveDefinitionTableDataResultDto.class);
    }

    //3. 组装流程信息
    String flowJson = definition.getFlowJson();

    approveDefinition.setBaseData(definitionBaseDataDto).setTableData(tableList).setFlowData(flowJson);
    return approveDefinition;
}
1.2.4 测试

启动服务,点击 表单设计/流程编辑/审批设置 , 查看是否可以查询出当前审批流程的数据,是否可以正常回显。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9gVYj23-1650203805197)(img/image-20210301115148293.png)]

在测试过程中, 会发现服务端抛出了异常:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZdYew2s-1650203805198)(img/image-20210319163510883.png)]

1.2.5 问题分析及解决

1). 出现该问题的原因

上述报出的错误 ,是在json转为实体类时, 属性 data 转换失败 , 原因是 data 在数据库中存储的就是一个字符串 , 类似于这种形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8TEU72oF-1650203805198)(img/image-20210319165037121.png)]

2). 解决方案

在进行json转换时, 调用的是JsonUtils工具类, 而工具类里是通过jackson来做的转换 , 我们可以通过配置jackson的反序列化器来完成这一块儿数据的转换:

A. 在 nineclock-common 模块中, 定义 DataDeserializer

public class DataDeserializer extends JsonDeserializer {
    @Override
    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        System.out.println(jsonParser.getText());
        return new ObjectMapper().readValue(jsonParser.getText(), Map.class);
    }
}

B. 在实体类中通过注解指定该反序列化方式

//组件数据设置
@JsonDeserialize(using = DataDeserializer.class)
private Map<String,Object> data;	

1.3 修改审批流程

1.3.1 思路分析

修改的操作思路, 参数传递, 和新增审批流程的思路比较像, 传递过来的参数直接 ApproveDefinitionDto 接收即可, 我们只需要根据页面传递的参数中是否包含ID, 来判定执行 新增 / 修改 操作即可。

ID存在: 新增

ID不存在: 修改

所以, 修改和新增我们可以采用同一个接口, 在Service逻辑处理时, 根据ID进行判定即可。

1.3.2 代码实现
//更新 -----------> ID 不存在的情况下 ;
// 根据ID查询流程定义对象
definition = approveDefinitionMapper.selectById(baseData.getId());

BeanUtil.copyProperties(baseData, definition, "allowUserJson");//基础数据赋值

definition.setAllowUserJson(JsonUtils.toString(baseData.getAllowUserJson()));//赋值允许操作用户

definition.setFormJson(JsonUtils.toString(approveDefinitionDto.getTableData()));//赋值表单信息


//需要将所有的表单信息, 记录下来并拓展参数 fieldKey, 字段主键
List<ColumnObjDto> columns = expandFieldKey(approveDefinitionDto);
definition.setColumns(JsonUtils.toString(columns));


//传递的流程数据中, 如果不包含nodeKey属性, 需要扩充
JSONArray jsonArray = JSONUtil.parseArray(approveDefinitionDto.getFlowData());
expandParamWithNodeKey(jsonArray);
definition.setFlowJson(JSONUtil.toJsonStr(jsonArray));

//更新数据
approveDefinitionMapper.updateById(definition);

完整版的代码实现 , 就查阅课程代码。

1.3.3 测试

启动服务,点击 表单设计/流程编辑/审批设置 , 审批流程数据回显,在回显回来的数据基础之上,对数据进行编辑, 查看编辑后的数据是否可以保存到数据库。

2. 动态构造Bpmn

2.1 问题思考

image-20210302181401095
  • 我们在审批管理中, 可以新建及修改审批,并通过流程设计器,自定义审批流程,我们所定义的审批流程在保存时,是直接保存到我们业务系统的数据库表approve_definition中,与工作流Activiti 没有任何关系。而我们前面在介绍审批中心的业务时,提到流程审批是借助于工作流引擎Activiti来实现的, 所以,在新增/修改审批流程时,需要将设计的流程部署到Activiti中。

  • 之前在Activiti的入门中,我们是通过idea的插件绘制了流程图 xxx.bpmn文件, 将该流程文件部署到Activiti中。而目前我们没有bpmn文件,有的只是流程数据(json格式)flowJson,这个时候,就需要使用 Activiti的API动态的构建Bpmn。

2.2 解决方案

在流程资源文件中,每一个节点元素在activiti中都有对应的元素属性承载类。

比如:

​ UserTask任务节点,它的元素属性承载类为UserTask

​ StartEvent开始节点的元素承载类为StartEvent

​ EndEvent结束节点的元素承载类为EndEvent

在bpmn流程文件进行解析时,也是将xml文档中每个元素解析为对应的承载类,从而进行组装成一个BmpnModel模型,我们可以直接由activiti将bpmn的xml文件解析为一个实例模型(下图所示),也可以完成通过自定义手动创建模型。

image-20210303084634917

2.3 Activiti动态构建Bpmn

在本章节中,我们将通过Activiti中提供的API完成业务流程的绘制及部署工作。

2.3.1 业务流程图

通过IDEA的BPMN流程设计器,设计流程,绘制流程图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O232oF33-1650203805199)(img/image-20210303085507118.png)]

2.3.2 流程文档XML描述

实际上,通过流程设计器设计出来的 xxx.bpmn文件本质上是一个XML文件,我们可以直接将文件的后缀名改为 xxx.xml,然后就可以直接打开XML文件,查看关于流程的描述。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" 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" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1614587433758" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
  <process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
    <startEvent id="_2" name="StartEvent"/>
    <userTask activiti:assignee="itheima" activiti:exclusive="true" id="_3" name="请假申请"/>
    <sequenceFlow id="_4" sourceRef="_2" targetRef="_3"/>
    <userTask activiti:assignee="admin" activiti:exclusive="true" id="_5" name="直属领导审批"/>
    <sequenceFlow id="_6" sourceRef="_3" targetRef="_5"/>
    <endEvent id="_7" name="EndEvent"/>
    <sequenceFlow id="_8" sourceRef="_5" targetRef="_7"/>
  </process>
  <bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
    <bpmndi:BPMNPlane bpmnElement="myProcess_1">
      <bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
        <omgdc:Bounds height="32.0" width="32.0" x="105.0" y="270.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
        <omgdc:Bounds height="55.0" width="85.0" x="275.0" y="260.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
        <omgdc:Bounds height="55.0" width="85.0" x="485.0" y="260.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7">
        <omgdc:Bounds height="32.0" width="32.0" x="695.0" y="270.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="_4" id="BPMNEdge__4" sourceElement="_2" targetElement="_3">
        <omgdi:waypoint x="136.9687194226713" y="285.0"/>
        <omgdi:waypoint x="220.0" y="285.0"/>
        <omgdi:waypoint x="275.0" y="285.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_3" targetElement="_5">
        <omgdi:waypoint x="360.0" y="287.5"/>
        <omgdi:waypoint x="485.0" y="287.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_5" targetElement="_7">
        <omgdi:waypoint x="570.0" y="285.0"/>
        <omgdi:waypoint x="620.0" y="285.0"/>
        <omgdi:waypoint x="695.0312805773287" y="285.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

在上述的XML描述中,主要包含两个部分:

  • 第一部分: 流程描述
  • 第二部分: 流程样式布局
2.3.3 手动绘制BpmnModel
@SpringBootTest
@RunWith(SpringRunner.class)
public class ActivitiDemoTest5 {

    @Autowired
    private RepositoryService repositoryService;

    @Test
    public void testDeploy(){
        //构造BpmnModel
        BpmnModel bpmnModel = new BpmnModel();

        //构造Process
        Process process = new Process();
        process.setId("p1");
        process.setName("p1");

        //创建StartEvent
        StartEvent startEvent = new StartEvent();
        startEvent.setId("s1");
        startEvent.setName("StartEvent");

        process.addFlowElement(startEvent);

        //创建UserTask
        UserTask userTask1 = new UserTask();
        userTask1.setId("u1");
        userTask1.setName("请假申请");
        userTask1.setAssignee("itheima");

        process.addFlowElement(userTask1);

        //创建SequenceFlow
        SequenceFlow sf1 = new SequenceFlow();
        sf1.setId("sf1");
        sf1.setSourceRef("s1");
        sf1.setTargetRef("u1");

        process.addFlowElement(sf1);

        //创建UserTask
        UserTask userTask2 = new UserTask();
        userTask2.setId("u2");
        userTask2.setName("直属领导审批");
        userTask2.setAssignee("admin");

        process.addFlowElement(userTask2);

        //创建SequenceFlow
        SequenceFlow sf2 = new SequenceFlow();
        sf2.setId("sf2");
        sf2.setSourceRef("u1");
        sf2.setTargetRef("u2");

        process.addFlowElement(sf2);

        //创建EndEvent
        EndEvent endEvent = new EndEvent();
        endEvent.setId("e1");
        endEvent.setName("EndEvent");

        process.addFlowElement(endEvent);

        //创建SequenceFlow
        SequenceFlow sf3 = new SequenceFlow();
        sf3.setId("sf3");
        sf3.setSourceRef("u2");
        sf3.setTargetRef("e1");

        process.addFlowElement(sf3);

        //将Process添加到BpmnModel
        bpmnModel.addProcess(process);

        //自动布局
        new BpmnAutoLayout(bpmnModel).execute();

        //输出XML
//        BpmnXMLConverter converter = new BpmnXMLConverter();
//        byte[] bytes = converter.convertToXML(bpmnModel);
//        System.out.println(new String(bytes));

        repositoryService.createDeployment()
                .name("请假审批流程")
                .addBpmnModel("请假审批流程.bpmn", bpmnModel)
                .deploy();
    }
}

关于动态构建的BpmnModel是否正常,是否和我们通过流程设计器设计出来的流程一致,可以通过BpmnXMLConverter将BpmnModel转为XML格式, 然后将XML文件转化为bpmn文件, 在流程设计器中打开, 进行验证。

3. 审批管理动态构造Bpmn

3.1 需求分析

在 新增 / 修改 审批流程时,不仅需要保存流程定义数据到业务系统表中,还需要根据页面传递的 流程数据 动态的生成Bpmn,构造BpmnModel,最终将流程部署到Activiti引擎中。

3.2 参数说明

流程定义数据以JSON数组的形式传输到Java后端,数据中通过type属性区分节点类型,对应图如下显示。

每一个节点,在数据封装时,都封装为了json数组中的一个对象,通过对象中的type属性,来区分是哪种类型的节点。

type含义封装类
start开始节点StartEvent
condition排他网关,内部包含不同流向的节点数据ExclusiveGateway
approval审批节点UserTask
CCList抄送节点UserTask
end结束节点EndEvent
cond具体的条件节点(不处理)-

整体的参数封装情况:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pAcPcTTx-1650203805200)(img/image-20210303104601629.png)]

内部网关的参数封装情况:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zAjIpbEH-1650203805200)(img/image-20210303110614450.png)]

3.3 流程设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h1nL5wWP-1650203805201)(img/image-20210303125720561.png)]

3.4 准备工作

引入流程处理中涉及到的工具类ActivitiUtils

public class ActivitiUtils {
	
   //key相关
   public static final String PROCESS_PREFIX = "process_";
   public static final String GATEWAY_PREFIX = "gateway_";
   public static final String USERTASK_PREFIX = "usertask_";
   public static final String USERS_PREFIX = "users_";
   public static final String SEQUENCEFLOW_PREFIX = "seq_";
   public static final String START_PREFIX = "start_";
   public static final String END_PREFIX = "end_";

   //流程定义中匹配相关
   public static final String COND_EXPRESS_USER_SYMBOL_DEPARTMENT = "inDepatment";
   public static final String COND_EXPRESS_USER_SYMBOL_USER = "inUser";
   public static final String PATTERN_START_USER_SYMBOL_EXPRESS = "\\$\\{startUserId\\}\\s*(inDepartment|inUser)\\s*\\[([^\\&\\&|\\|\\||\\(|\\)]*)\\]";
   public static final String PATTERN_CURR_USER_SYMBOL_EXPRESS = "\\$\\{currUserId\\}\\s*(inDepartment|inUser)\\s*\\[([^\\&\\&|\\|\\||\\(|\\)]*)\\]";
   public static final String PATTERN_GET_PRIORITY = "\\$\\{priority\\}='(\\d+)'";


   public static String getKey(String type,String sufix) {
      if("process".equals(type)) {
         return PROCESS_PREFIX + sufix;
      }else if ("condition".equals(type)) {
         return GATEWAY_PREFIX + sufix;
      }else if (Arrays.asList("CCList", "approval").contains(type)) {
         return USERTASK_PREFIX + sufix;
      }else if ("start".equals(type)) {
         return START_PREFIX + sufix;
      }else if ("end".equals(type)) {
         return END_PREFIX + sufix;
      }else if ("seq".equals(type)) {
         return SEQUENCEFLOW_PREFIX + sufix;
      }
      return null;
   }
}

3.4 完善代码

1). ActivitiService

public interface ActivitiService {
    /**
     * 动态部署审批流程到Activiti
     * @param definition
     */
    public void deployProcessToActiviti(ApproveDefinition definition);
}

2). ActivitiServiceImpl

@Service
@Slf4j
public class ActivitiServiceImpl implements ActivitiService {
    @Autowired
    private RepositoryService repositoryService;
	
    @Override
    public void deployProcessToActiviti(ApproveDefinition definition) {
        //1. 构建BpmnModel
        BpmnModel bpmnModel = new BpmnModel();

        //2. 创建Process
        Process process = new Process();
        process.setId("process_"+definition.getId());
        process.setName(definition.getName());

        //3. 创建流程节点(开始节点, 审批节点, 网关节点 , 抄送节点,  结束节点)
        JSONArray flowArray = JSONUtil.parseArray(definition.getFlowJson());
        addNodesToProcess(process, flowArray);

        //4. 设置Process到BpmnModel
        bpmnModel.addProcess(process);

        //5. 自动布局BpmnModel
        new BpmnAutoLayout(bpmnModel).execute();

        //6. 部署构建的审批流程BpmnModel到Activiti
        repositoryService.createDeployment()
                .name(definition.getName())
                .addBpmnModel(definition.getName()+".bpmn", bpmnModel)
                .deploy();
		
        //输出XML, 便于测试
        BpmnXMLConverter converter = new BpmnXMLConverter();
        byte[] bytes = converter.convertToXML(bpmnModel);

        log.info("-------> 转换BpmnModel为XML数据 .");
        System.out.println(new String(bytes));
        log.info("-------> XML文件转换并输出完毕 . ");
    }
	
    //构建节点, 添加到process中
    private void addNodesToProcess(Process process, JSONArray flowArray) {

        for (int i = 0; i < flowArray.size(); i++) {
            JSONObject currNode = (JSONObject) flowArray.get(i);
            JSONObject nextNode = null;
            
            if(i < flowArray.size()-1){
                nextNode = (JSONObject)flowArray.get(i+1);
            }

            //传递当前节点 , 下一个节点, 进行流程绘制 ------> 不同的节点类型调用的API不同, 绘制的方式不同 , 需要根据type来决定
			//TODO 
            
        }
    }
}

3.5 流程节点绘制

3.5.1 类图设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4VpkMXHT-1650203805201)(img/image-20210303130554867.png)]

作用type
ProcessNodeResolver抽象类, 定义流程节点创建方法, 及公用方法-
StartNodeResolver子类, 用于处理开始节点start
CCListNodeResolver子类, 用于处理抄送节点CCList
ApproveNodeResolver子类, 用于处理审批节点approval
ConditionNodeResolver子类, 用于处理网关节点condition
EndNodeResolver子类, 用于处理结束节点end
3.5.2 抽象类
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.nineclock.approve.util.ActivitiUtils;
import com.nineclock.common.utils.UUIDUtils;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.Process;

/**
 * 流程节点抽象类
 */
public abstract class ProcessNodeResolver {

    /**
     * 添加节点到流程Process中
     * @param process
     * @param currNode
     * @param nextNode
     */
    public abstract void addFlowNodeToActiviti(Process process, JSONObject currNode, JSONObject nextNode);

    /**
     * 获取当前节点的Key/ID
     * @param currNode
     * @return
     */
    protected String getNodeId(JSONObject currNode) {
        String type = currNode.getStr("type");
        String nodekey = currNode.getStr("nodeKey");
        return ActivitiUtils.getKey(type,nodekey);
    }

    /**
     * 创建流程图中的线条
     *
     * @param process 流程对象
     * @param currNode 当前节点
     * @param nextNode 下一个节点
     * @param cond 条件节点, 没有 , 直接传递 null
     * @return
     */
    protected SequenceFlow newSequenceFlow(Process process, JSONObject currNode, JSONObject nextNode){
        SequenceFlow sf = new SequenceFlow();
        sf.setId(ActivitiUtils.getKey("seq", UUIDUtils.getUUID()));
        sf.setSourceRef(getNodeId(currNode));
        sf.setTargetRef(getNodeId(nextNode));

        process.addFlowElement(sf);
        return sf;
    }
    
}
3.5.3 节点处理

1). StartNodeResolver

/**
 * 开始节点流程处理器
 */
public class StartNodeResolver  extends ProcessNodeResolver {

    @Override
    public void addFlowNodeToActiviti(Process process, JSONObject currNode, JSONObject nextNode) {
        //创建开始节点
        StartEvent startEvent = new StartEvent();
        startEvent.setId(getNodeId(currNode));
        startEvent.setName(currNode.getStr("name"));

        process.addFlowElement(startEvent);

        //绘制线条, 指向下一个节点
        newSequenceFlow(process, currNode, nextNode);
    }
}

2).CCListNodeResolver

/**
 * 抄送节点解析处理器
 */
public class CCListNodeResolver  extends ProcessNodeResolver {
    @Override
    public void addFlowNodeToActiviti(Process process, JSONObject currNode, JSONObject nextNode) {

        //创建抄送节点
        UserTask userTask = new UserTask();
        userTask.setId(getNodeId(currNode));
        userTask.setName(currNode.getStr("name"));
        userTask.setCategory("2");// 抄送, 知会

        process.addFlowElement(userTask);

        //绘制连线
        newSequenceFlow(process, currNode, nextNode);
    }
}

3).ApproveNodeResolver

/**
 * 审批节点解析处理器
 */
public class ApproveNodeResolver extends ProcessNodeResolver {

    @Override
    public void addFlowNodeToActiviti(Process process, JSONObject currNode, JSONObject nextNode) {

        //创建任务节点
        UserTask userTask = new UserTask();
        userTask.setId(getNodeId(currNode));
        userTask.setName(currNode.getStr("name"));
        userTask.setCategory("1");//审批

        
        process.addFlowElement(userTask);
        newSequenceFlow(process, currNode, nextNode);
    }
}

4). EndNodeResolver

/**
 * 结束节点流程处理器
 */
public class EndNodeResolver  extends ProcessNodeResolver {

    @Override
    public void addFlowNodeToActiviti(Process process, JSONObject currNode, JSONObject nextNode) {

        //创建结束节点
        EndEvent endEvent = new EndEvent();
        endEvent.setId(getNodeId(currNode));
        endEvent.setName(currNode.getStr("name"));
		 
        process.addFlowElement(endEvent);
    }
}
3.5.4 工厂类

该工厂中维护了类型与节点解析处理类的关系, 通过工厂方法可以获取到能够处理该type的ProcessNodeResolver。

/**
 * ProcessNodeResolver 工厂类
 */
public class ProcessNodeResolverFactory {
    
    private static Map<String, ProcessNodeResolver> instances = new HashMap<String, ProcessNodeResolver>();
    
    static {
        instances.put("start".toUpperCase(), new StartNodeResolver());
        instances.put("cclist".toUpperCase(), new CCListNodeResolver());
        instances.put("end".toUpperCase(), new EndNodeResolver());
        instances.put("approval".toUpperCase(), new ApproveNodeResolver());
    }

    //根据type, 获取能够处理当前类型节点的 节点处理器
    public static ProcessNodeResolver getInstance(String type){
        return instances.get(type.toUpperCase());
    }
}

完善ActivitiServiceImpl中的addNodesToProcess方法代码:

根据类型获取对应的ProcessNodeResolver , 然后创建对应的节点, 添加到Process中

//构建节点, 添加到process中
private void addNodesToProcess(Process process, JSONArray flowArray) {

    for (int i = 0; i < flowArray.size(); i++) {

        JSONObject currNode = (JSONObject) flowArray.get(i);
        JSONObject nextNode = null;

        //网关的条件并不是真正的节点
        if("cond".equals(currNode.getStr("type")))continue;

        if(i < flowArray.size()-1){
            nextNode = (JSONObject)flowArray.get(i+1);
        }

        //传递当前节点 , 下一个节点, 进行流程绘制 ------> 不同的节点类型调用的API不同, 绘制的方式不同 , 需要根据type来决定
        ProcessNodeResolver nodeResolver = ProcessNodeResolverFactory.getInstance(currNode.getStr("type"));
        nodeResolver.addFlowNodeToActiviti(process,currNode,nextNode);
    }
}
3.5.5 测试

保存审批流程

在ApproveDefinitionServiceImpl 中 添加审批流程时 ,保存信息到 activiti 中

approveDefinitionMapper.insert(approveDefinition);
//保存 审批流程 到 activiti中 
activitiService.deployProcessToActiviti(approveDefinition);

修改审批流程

修改审批流程,先删除审批流程, 在ActivitiService 定义dropProcessFromActiviti 删除审批流程的方法

ActivitiService

/**
 * 删除 审批流程
 * @param definition
 */
public void dropProcessFromActiviti(ApproveDefinition definition);

ActivitiServiceImpl

@Override
public void dropProcessFromActiviti(ApproveDefinition definition) {
    //根据流程 key查询流程
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("process_" + definition.getId()).singleResult();
    //根据 id删除 流程
    repositoryService.deleteDeployment(processDefinition.getDeploymentId());
}

调整修改审批流程

//根据id更新 审批流程
approveDefinitionMapper.updateById(approveDefinition);
//删除activiti 中 审批流程
activitiService.dropProcessFromActiviti(approveDefinition);
//部署审批流程 到 Activiti
activitiService.deployProcessToActiviti(approveDefinition);

当保存完毕流程定义之后, 检查一下看看Activiti引擎的表结构中, 是否存在对应的流程数据 ;

也可以通过输出的流程定义的 XML , 查看动态绘制的流程图是否正确 。

4. 移动端审批介绍

1). 发起审批

在移动端, 用户点击 “工作台” , 就可以看到审批中心 ; 在审批中心中, 用户可以根据需要, 发起对应的审批流程 ;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1tKwIuMM-1650203805202)(img/image-20210319183819792.png)]

2). 我发起的

在 “我发起的” 栏目中, 可以查看到当前登录用户发起的审批流程 ; 也能够查看当前流程具体走到哪一节点 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-goHciI2d-1650203805203)(img/image-20210319191019330.png)]

3). 我审批的

在 “我审批的” 栏目中, 可以查看到当前登录用户需要处理的审批流程 ; 也能够查看当前流程具体走到哪一节点 , 也可以针对于当前的审批流程进行审批通过/驳回等操作 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wzlA1jP-1650203805203)(img/image-20210319191650112.png)]

上述功能实现APP端都已经完成了 , 而关于服务端的接口, 我们实现了一部分 ; 关于流程的发起 、审批 实际上就是结合Activiti工作流中的API来完成节点任务、查询指定任务, 这部分的功能我们就不再课上实现了。

今日作业

  • 完成流程审批管理中的接口开发

    • 根据ID查询流程定义接口
    • 编辑流程定义, 并保存修改
  • 基于Activiti完成Bpmn流程文件的动态构造, 具体的流程图如下:

    image-20210303141028963
  • 基于Activiti的Bpmn流程动态构造, 完成审批管理中审批流程定义保存/修改时, 动态构建Bpmn并部署到Activiti

    • 先理清楚实现流程
    • 再考虑结构设计
    • 完成各个类型节点处理
    • 整合联调测试
  • 自主完成, activite的Bpmn的修改流程

Definition);


当保存完毕流程定义之后, 检查一下看看Activiti引擎的表结构中, 是否存在对应的流程数据 ;

也可以通过输出的流程定义的 XML , 查看动态绘制的流程图是否正确 。





## 4. 移动端审批介绍

**1). 发起审批**

在移动端, 用户点击 "工作台" , 就可以看到审批中心 ; 在审批中心中, 用户可以根据需要, 发起对应的审批流程 ;

[外链图片转存中...(img-1tKwIuMM-1650203805202)] 



**2). 我发起的**

在 "我发起的" 栏目中, 可以查看到当前登录用户发起的审批流程 ; 也能够查看当前流程具体走到哪一节点 。

[外链图片转存中...(img-goHciI2d-1650203805203)] 



**3). 我审批的**

在 "我审批的" 栏目中, 可以查看到当前登录用户需要处理的审批流程 ; 也能够查看当前流程具体走到哪一节点 , 也可以针对于当前的审批流程进行审批通过/驳回等操作 。

[外链图片转存中...(img-4wzlA1jP-1650203805203)] 



上述功能实现APP端都已经完成了 , 而关于服务端的接口, 我们实现了一部分 ; 关于流程的发起 、审批 实际上就是结合Activiti工作流中的API来完成节点任务、查询指定任务, 这部分的功能我们就不再课上实现了。





























  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值