引言
activiti原生的编辑器不能实现A8一样的可视化设置UserTask节点的受理人,但是业务需求是想要让用户可以动态的设置每个流程图的受理人,所以开发了节点设置审批人的功能,其中后台有职位表,用来指定职位对应的用户关系。
本文参考了这篇博客的部分代码。
下面是代码:
- 后台模型
@Data
public class ProcessDefinition extends BaseEntity {
private String id;
@Excel(name = "流程名称")
private String name;
@Excel(name = "流程KEY")
private String key;
@Excel(name = "流程版本")
private int version;
@Excel(name = "所属分类")
private String category;
@Excel(name = "流程描述")
private String description;
private String deploymentId;
@Excel(name = "部署时间", databaseFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
private Date deploymentTime;
@Excel(name = "流程图")
private String diagramResourceName;
@Excel(name = "流程定义")
private String resourceName;
/** 流程实例状态 1 激活 2 挂起 */
private String suspendState;
private String suspendStateName;
private List<UserTaskModel> userTaskList;
}
- 用户节点模型
@Data
public class UserTaskModel implements Serializable {
private String id;
private String name;
/**
* 审批人类型
* 0:未指定
* 1:办理人
* 2:候选用户
* 3:候选组
*/
private String type;
/**
* 办理人,办理人只能指定一个人,不能使用逗号分隔。
* 默认执行签收操作taskService.claim(taskId, currentUserId);
* 在ACT_HI_TASKINST和ACT_RU_TASK会产生数据,这两个表里面的Assignee_字段就是设置的办理人姓名或者对象的ID
*/
private String assignee;
/**
* 候选用户,候选用户设置办理人不是很多的情况下使用,而且需要签收,
* 也就是说我们常说的抢件模式,设置候选组的前提是没有指定Assignee,(即没有执行签收操作)。
* 设置候选用户需要主动签收taskService.claim(taskId, currentUserId);
*/
private String candidateUsers;
/**
* 候选组,这个就是这只办理角色或者办理岗位,适合使用在办理人比较多的情况下,而且涉及到几个部门的情形。
* 候选组与候选用户类似,只是要获取候选用户,需要根据候选组找到对应的用户
* 设置候选组需要主动签收taskService.claim(taskId, currentUserId);
*/
private String candidateGroups;
}
- 后台查询流程图所有节点及属性方法
public List<UserTaskModel> getUserTaskList(String deploymentId) throws Exception {
byte[] byteArray = this.getByteArray(deploymentId);
SAXReader saxReader = new SAXReader();
// 获取流程图文件中的userTask节点的所有属性
ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
Document document = saxReader.read(bis);
Element rootElement = document.getRootElement();
Element process = rootElement.element("process");
List<Element> userTaskList = process.elements("userTask");
ArrayList<UserTaskModel> list = Lists.newArrayList();
// 包装成适合前端展示的集合并返回
for (Element element : userTaskList) {
UserTaskModel userTaskModel = new UserTaskModel();
userTaskModel.setId(element.attributeValue("id"));
userTaskModel.setName(element.attributeValue("name"));
String type = "0";
String assignee = element.attributeValue("assignee");
String candidateUsers = element.attributeValue("candidateUsers");
String candidateGroups = element.attributeValue("candidateGroups");
if (StringUtils.isNotEmpty(candidateGroups)) {
type = "3";
userTaskModel.setCandidateGroups(candidateGroups);
}
if (StringUtils.isNotEmpty(candidateUsers)) {
type = "2";
userTaskModel.setCandidateUsers(candidateUsers);
}
if (StringUtils.isNotEmpty(assignee)) {
type = "1";
userTaskModel.setAssignee(assignee);
}
userTaskModel.setType(type);
list.add(userTaskModel);
}
bis.close();
return list;
}
private byte[] getByteArray(String deploymentId) throws IOException {
// 从ACT_GE_BYTEARRAY表中获取流程图的二进制文件
// 此操作对象必须是已部署的模型,此时流程定义的二进制文件才是以bpmn20.xml结尾的。
String sql =
"select a.* from ACT_GE_BYTEARRAY a where NAME_ LIKE '%bpmn20.xml' and " +
"DEPLOYMENT_ID_= ? ";
final LobHandler lobHandler = new DefaultLobHandler(); // reusable
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
jdbcTemplate.query(sql, new Object[]{deploymentId},
new AbstractLobStreamingResultSetExtractor<Object>() {
public void streamData(ResultSet rs) throws SQLException, IOException {
FileCopyUtils.copy(lobHandler.getBlobAsBinaryStream(rs, "BYTES_"), bos);
}
}
);
byte[] bytes = bos.toByteArray();
bos.close();
return bytes;
}
- 前台页面伪代码
<el-table @selection-change="handleSelectionChange" :data="tableData" style="width: 100%; margin-top: 10px">
<el-table-column prop="id" label="流程ID" width="180" fixed show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="key" label="流程Key" width="80" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="name" label="流程名称" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="description" label="流程描述" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="category" label="所属分类" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="deploymentTime" label="部署时间" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="resourceName" label="流程定义" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="diagramResourceName" label="流程图" show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="suspendState" label="流程定义状态" show-overflow-tooltip="true" align="center">
<template slot-scope="scope">
<span v-if="scope.row.suspendState !== null">
<el-tag type="success">已激活</el-tag>
</span>
<span v-else>
<el-tag type="info">未激活</el-tag>
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini"
@click="convertModel(scope.row.id)">转模型
</el-button>
<el-button type="primary" size="mini"
@click="handleTaskList(scope.row)">节点列表
</el-button>
<el-button type="danger" size="mini"
@click="handleDelete(scope.row.deploymentId)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 卡片区域 -->
<el-dialog
title="节点受理人列表"
:visible.sync="devOpsDialogVisible"
append-to-body=true
width="40%">
<el-table :data="userTaskList" style="width: 100%" border>
<el-table-column prop="name" label="节点名称" min-width="120" fixed="left" align="center"/>
<el-table-column prop="type" label="受理人类型" min-width="100" align="center" :formatter="formatType" />
<el-table-column prop="assignee" label="审批人" min-width="120" align="center" :formatter="formatUser"/>
<el-table-column prop="candidateUsers" label="候选人" min-width="120" align="center" :formatter="formatUser"/>
<el-table-column prop="candidateGroups" label="候选组" min-width="100" align="center" :formatter="formatJob"/>
<el-table-column label="操作" fixed="right" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleNodeModify(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<p style="color:#F56C6C">审批人,候选人,候选组只可设置其中一种。覆盖优先级:审批人>候选人>候选组</p>
<el-button size="normal" @click="handleCancleModify()">取消</el-button>
<el-button type="primary" size="normal" @click="handleTaskModify()">保存</el-button>
</el-dialog>
<el-dialog
title="节点受理人修改"
:visible.sync="nodeOpsDialogVisible"
append-to-body=true
width="30%">
<el-form ref="nodeForm" :model="nodeData" label-width="60px">
<el-form-item label="节点名称">
<el-input v-model="nodeData.name" disabled/>
</el-form-item>
<el-form-item label="受理人类型">
<el-select v-model="nodeData.type" placeholder="受理人类型" @change="handleTypeChange(nodeData)">
<el-option label="未指定" value="0"/>
<el-option label="审批人" value="1"/>
<el-option label="候选人" value="2"/>
<el-option label="候选组" value="3"/>
</el-select>
</el-form-item>
<el-form-item label="审批人" v-if="nodeData.type==1">
<el-select v-model="nodeData.assignee" filterable clearable>
<el-option v-for="item in this.userList" :key="item.id" :label="item.userAccount" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="候选人" v-else-if="nodeData.type==2">
<el-select v-model="nodeData.candidateUsers" filterable clearable>
<el-option v-for="item in this.userList" :key="item.id" :label="item.userAccount" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="候选组" v-else-if="nodeData.type==3">
<el-select v-model="nodeData.candidateGroups" placeholder="候选组">
<el-option v-for="item in this.jobList" :key="item.id" :label="item.name" :value="item.code"/>
</el-select>
</el-form-item>
<el-form-item label="未指定" v-else>
<el-input disabled/>
</el-form-item>
</el-form>
<p style="color:#F56C6C">审批人,候选人,候选组只可设置其中一种。覆盖优先级:审批人>候选人>候选组</p>
<el-button type="primary" size="normal" @click="handleNodeChange()">确定</el-button>
</el-dialog>
export default {
name: "index",
data() {
return {
processDefinition: null,
devOpsDialogVisible: false,
nodeOpsDialogVisible: false,
deploymentIds: "",
userTaskList: [],
jobList: [],
userList: [],
tableData: [],
nodeData: {},
};
},
mounted() {
this.getList();
this.getJobList();
this.getUserList('');
},
methods: {
getList() {
var listQuery = this.listQuery;
const token = util.cookies.get("token");
this.axios({
url: "http://localhost:8003/api/bpm/definition/definitions",
params: { listQuery },
headers: {
token: token,
},
method: "get",
}).then((res) => {
this.tableData = res.data.data;
this.total = res.data.count;
for (var i = 0; i < this.tableData.length; i++) {
// this.tableData[i].deploymentTime = formatTimeToStr(this.tableData[i].deploymentTime, 'yyyy-MM-dd hh:mm:ss')
}
});
},
getJobList() {
const token = util.cookies.get("token");
this.axios({
url: "http://localhost:8021/api/v1/sys/job/select",
data: {},
headers: {
token: token,
},
method: "post",
}).then((res) => {
this.jobList = res.data.data;
});
},
getUserList(param) {
const token = util.cookies.get("token");
this.axios({
url: "http://localhost:8021/api/v1/sys/user/list/select?param=" + param,
data: {},
headers: {
token: token,
},
method: "post",
}).then((res) => {
this.userList = res.data.data;
});
},
handleNodeModify(data) {
this.nodeOpsDialogVisible = true;
this.nodeData = data;
},
handleTaskList(data) {
const token = util.cookies.get("token");
this.devOpsDialogVisible = true;
this.processDefinition = data;
this.axios({
url: "http://localhost:8003/api/bpm/definition/nodes/" + data.deploymentId,
data: {},
headers: {
token: token,
},
method: "post",
}).then((res) => {
if (res.data.code === 200) {
this.userTaskList = res.data.data;
} else {
that.$message.error("修改失败");
}
});
},
handleTaskModify() {
this.processDefinition.userTaskList = this.userTaskList;
const token = util.cookies.get("token");
this.axios({
url: "http://localhost:8003/api/bpm/definition/modify",
data: this.processDefinition,
headers: {
token: token,
},
method: "post",
}).then((res) => {
if (res.data.code === 200) {
this.$message({
showClose: true,
message: "修改成功",
type: "success",
});
}
});
this.userTaskList = [];
this.processDefinition = null;
this.devOpsDialogVisible = false;
this.getList;
},
handleCancleModify() {
this.userTaskList = [];
this.processDefinition = null;
this.devOpsDialogVisible = false;
this.nodeData = [];
},
handleNodeChange(){
this.processDefinition.userTaskList = this.userTaskList;
this.nodeOpsDialogVisible = false;
},
handleTypeChange(item){
item.assignee = null;
item.candidateUsers = null;
item.candidateGroups = null;
},
formatType(row, cloumn) {
if(row.type == 0){
return '未指定'
}
if(row.type == 1){
return '审批人'
}
if(row.type == 2){
return '候选人'
}
if(row.type == 3){
return '候选组'
}
},
formatAssignee(row, cloumn) {
if(row.assignee === null) {
return null;
}
for(var i=0;i<this.userList.length;i++){
if(this.userList[i].id === row.assignee){
return this.userList[i].userAccount;
}
}
if(row.assignee != null) {
return row.assignee;
}
},
formatCandidateUsers(row, cloumn) {
if(row.candidateUsers === null) {
return null;
}
for(var i=0;i<this.userList.length;i++){
if(this.userList[i].id === row.candidateUsers){
return this.userList[i].userAccount;
}
}
if(row.candidateUsers != null) {
return row.candidateUsers;
}
},
formatJob(row, cloumn) {
if(row.candidateGroups === null) {
return null;
}
for(var i=0;i<this.jobList.length;i++){
if(this.jobList[i].code === row.candidateGroups){
return this.jobList[i].name;
}
}
return row.candidateGroups;
},
},
};
-
实际页面
-
页面修改后,后端代码
public void modify(com.jswPlatform.bpm.model.act.ProcessDefinition processDefinition) throws Exception {
String deploymentId = processDefinition.getDeploymentId();
byte[] byteArray = this.getByteArray(deploymentId);
SAXReader saxReader = new SAXReader();
saxReader.setEncoding("UTF-8");
ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
// 获取所有用户节点
Document document = saxReader.read(bis);
Element rootElement = document.getRootElement();
Element process = rootElement.element("process");
List<Element> userTaskNodeList = process.elements("userTask");
List<UserTaskModel> list = processDefinition.getUserTaskList();
// 遍历对应节点id,修改节点属性
for (Element element : userTaskNodeList) {
String id = element.attributeValue("id");
UserTaskModel userTaskModel =
list.stream().filter(u -> u.getId().equals(id)).collect(Collectors.toList()).get(0);
String type = userTaskModel.getType();
Attribute assignee = element.attribute("assignee");
if (assignee != null) {
element.remove(assignee);
}
Attribute candidateUsers = element.attribute("candidateUsers");
if (candidateUsers != null) {
element.remove(candidateUsers);
}
Attribute candidateGroups = element.attribute("candidateGroups");
if (candidateGroups != null) {
element.remove(candidateGroups);
}
switch (type) {
case "0":
break;
case "1":
element.addAttribute("activiti:assignee", userTaskModel.getAssignee());
break;
case "2":
element.addAttribute("activiti:candidateUsers", userTaskModel.getCandidateUsers());
break;
case "3":
element.addAttribute("activiti:candidateGroups", userTaskModel.getCandidateGroups());
break;
}
}
// 将修改后的流程图文件转为字节数组
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLWriter xmlWriter = new XMLWriter(out);
xmlWriter.write(document);
xmlWriter.flush();
final byte[] bytes = out.toByteArray();
// 将字节数组写回数据库
LobHandler lobHandler = new DefaultLobHandler();
String sql = "update ACT_GE_BYTEARRAY set BYTES_=? where NAME_ LIKE '%bpmn20.xml' and " +
"DEPLOYMENT_ID_= ? ";
jdbcTemplate.execute(sql, new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
@Override
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException {
lobCreator.setBlobAsBytes(ps, 1, bytes);
ps.setString(2, deploymentId);
}
});
/**流程定义发布后,若你使用了该流程,其就会在缓存中缓存了流程定义的解析后的对象,供整个引擎使用,
* 这时你更改了流程定义的XML后,那份缓存并没有实现了更改,因此,需要告诉引擎,让他清空缓存中的该流程定义
*/
((ProcessEngineConfigurationImpl)processEngineConfiguration).getProcessDefinitionCache().remove(deploymentId);
}