第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 问题思考
-
我们在审批管理中, 可以新建及修改审批,并通过流程设计器,自定义审批流程,我们所定义的审批流程在保存时,是直接保存到我们业务系统的数据库表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文件解析为一个实例模型(下图所示),也可以完成通过自定义手动创建模型。
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流程文件的动态构造, 具体的流程图如下:
-
基于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来完成节点任务、查询指定任务, 这部分的功能我们就不再课上实现了。