简介
上一章咱们把环境和数据库搞定了,这章就说下怎么新建一个审批模板,怎么部署模板,怎么发起一个审批任务,怎么删除审批模板怎么查看待审批任务列表。
新建审批模板
比如我们需要报销,提交一个申请,然后提交给领导审批,审批完毕后,报销就算结束了。
流程图如下
设置审批模板属性
package com.glj.activiti.config;
import lombok.extern.log4j.Log4j2;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.stereotype.Component;
@Log4j2
@Component
public class ShareniuProcessEngineConfigurationConfigurer implements ProcessEngineConfigurationConfigurer {
public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
processEngineConfiguration.setActivityFontName("宋体");
processEngineConfiguration.setAnnotationFontName("宋体");
processEngineConfiguration.setActivityFontName("宋体");
log.info("ShareniuProcessEngineConfigurationConfigurer配置的字体#############"+processEngineConfiguration.getActivityFontName());
}
}
部署流程
为了使我们的流程被Activiti Engine所知,我们需要部署该流程
package com.glj.activiti.controller;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.glj.activiti.enums.ResponseCode;
import com.glj.activiti.dto.AuditAddUserRequest;
import com.glj.activiti.dto.StartProcessRequest;
import com.glj.activiti.response.JsonResult;
import com.glj.activiti.response.TaskResponse;
import com.glj.activiti.service.IGroupService;
import com.glj.activiti.service.IProcDefService;
import com.glj.activiti.service.IUserService;
import com.glj.activiti.vo.Group;
import com.glj.activiti.vo.User;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
@RequestMapping("/taskController")
public class TaskController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private IUserService userService;
@Autowired
private TaskService taskService;
@Autowired
private IProcDefService procDefService;
@Autowired
private IGroupService groupservice;
@ApiOperation(value ="部署流程",notes = "添加用户申请的审核处理")
@PostMapping(value ="/deployProcess")
public JsonResult deployProcess(@RequestParam("tenantId") String tenantId, @RequestParam(name="category") String category,
@RequestParam(name="fileName") String fileName, @RequestPart("file") MultipartFile file) {
return procDefService.deployProcDef(category, file, tenantId, fileName);
}
}
部署类型为zip,bar,png,bmmn
@Override
public JsonResult deployProcDef(String category, MultipartFile file, String tenantId, String fileName) {
LOGGER.info("deploy(category={}, file={}, tenantId={}, name={})", category, file.getName(), tenantId, fileName);
InputStream fileInputStream = null;
JsonResult result = new JsonResult();
try {
fileInputStream = file.getInputStream();
} catch (IOException e) {
result.setCode(ResponseCode.ERROR.getValue()).setErrMsg(e.getMessage());
}
Deployment deployment = null;
String basename = FilenameUtils.getBaseName(fileName);
String extension = FilenameUtils.getExtension(fileName);
if (extension.equals("zip") || extension.equals("bar")) {
ZipInputStream zip = new ZipInputStream(fileInputStream);
deployment = this.repositoryService.createDeployment().name(basename).category(category).tenantId(tenantId)
.addZipInputStream(zip).deploy();
} else if (extension.equals("png")) {
deployment = this.repositoryService.createDeployment().category(category).tenantId(tenantId).name(file.getName())
.addInputStream(fileName, fileInputStream).deploy();
} else if (fileName.indexOf("bpmn20.xml") != -1) {
deployment = this.repositoryService.createDeployment().name(basename).category(category).tenantId(tenantId)
.addInputStream(fileName, fileInputStream).deploy();
} else if (extension.equals("bpmn")) { // bpmn扩展名特殊处理,转换为bpmn20.xml
String baseName = FilenameUtils.getBaseName(fileName);
deployment = this.repositoryService.createDeployment().name(basename).category(category).tenantId(tenantId)
.addInputStream(baseName + ".bpmn20.xml", fileInputStream).deploy();
}
List list = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId()).list();
if (CollectionUtils.isEmpty(list)) {
result.setCode(ResponseCode.ERROR.getValue()).setErrMsg("流程部署失败");
}else {
result.setCode("scucess");
result.setContent(deployment);
}
return result;
}
部署意味着引擎将解析BPMN流程定义文件并将其转换为可执行文件。此外,还会将记录添加到每个部署的Repository表中。之后,我们可以查询Repository服务从而获取已部署的流程:也就是ProcessDefinitions。
发起任务审批
部署完毕后,就可以发起流程了。
@ApiOperation(value ="发起任务审批",notes = "发起任务审批")
@PostMapping(value ="/startProcesses")
public JsonResult startProcesses(@RequestBody StartProcessRequest request) {
JsonResult result = new JsonResult();
try {
User user = userService.findUserById(request.getId());
Group currentGroup=groupservice.findGroupById(request.getGroupId());
ConcurrentHashMap variables =new ConcurrentHashMap();
variables.put("userId",user.getId());
String processId= runtimeService.startProcessInstanceByKeyAndTenantId("firstProcess",variables,request.getTenantId()).getId();
String auditMsg = "管理员";
// 查询到当前发起人的上一级
List groupList = groupservice.findGroupByType( currentGroup.getType()+1);
if(groupList!=null&&groupList.size()>0){
auditMsg= groupList.get(0).getName();
}
log.info("***************发起审批申请开始***************");
List tasks = taskService.createTaskQuery().taskAssignee(user.getId()).list();
log.info("待处理任务数:"+tasks.size());
for (Task task : tasks) {
log.info(user.getId() + "的任务taskname:" + task.getName() + ",id:" + task.getId());
taskService.setVariable(task.getId(), "msg", auditMsg);//设置审批类型
taskService.complete(task.getId());//完成任务
log.info("需要做的下一步审批:" + "审批意见");
}
log.info("***************提交添加用户申请完成***************");
user.setState(0);
//修改状态
user.setAuditState("审核中");
user.setProcessId(processId);
user = userService.updateState(user);
if(user!=null){
result.setCode(ResponseCode.SUCCESS.getValue());
}else{
result.setCode(ResponseCode.ERROR.getValue()).setErrMsg("添加用户失败");
}
} catch (Exception e) {
log.error(e.getMessage());
result.setCode(ResponseCode.ERROR.getValue()).setErrMsg(e.getMessage());
}
return result;
}
获取待办任务列表
@ApiOperation(value ="获取流程待办任务列表",notes = "分页展示流程待办任务基础信息")
@PostMapping(value ="/todoList}")
public JsonResult todoList(@RequestBody StartProcessRequest request){
JsonResult result = new JsonResult();
//取出用户,角色信息
Group currentGroup=groupservice.findGroupById(request.getGroupId());
TaskQuery query = taskService.createTaskQuery().taskCandidateGroup(currentGroup.getId());
// 通过人取审批列表
// query = this.taskService.createTaskQuery().taskAssignee(request.getId()).active()
// .includeProcessVariables().orderByTaskCreateTime().desc();
List list =query.list();
List responses =new ArrayList();
for (Task task : list) {
TaskResponse response =new TaskResponse();
BeanUtils.copyProperties(task,response);
responses.add(response);
}
result.setCode(ResponseCode.SUCCESS.getValue()).setContent(responses);
return result;
}
审批人审核任务
@ApiOperation(value ="待办任务处理审核",notes = "添加用户申请的审核处理")
@PostMapping(value ="/audit/add_user")
public JsonResult auditAddUser(@RequestBody AuditAddUserRequest auditAddUserRequest) {
JsonResult result = new JsonResult();
try {
//首先根据ID查询任务
Task task=taskService.createTaskQuery() // 创建任务查询
.taskId(auditAddUserRequest.getTaskId()) // 根据任务id查询
.singleResult();
Map variables=new HashMap();
// 取用户信息,更新审核状态
String userId=(String) taskService.getVariable(auditAddUserRequest.getTaskId(), "userId");
User user=userService.findUserById(userId);
Group currentGroup=groupservice.findGroupById(user.getGroupIds());
if(auditAddUserRequest.getState()==1){
user.setAuditState("审核通过");
user.setState(1);
variables.put("msg", "通过");
}else{
user.setAuditState("审核未通过");
user.setState(-1);
variables.put("msg", "未通过");
}
// 更新审核信息
userService.updateState(user);
// 获取流程实例id
String processInstanceId=task.getProcessInstanceId();
// 添加批注信息
taskService.addComment(auditAddUserRequest.getTaskId(), processInstanceId, auditAddUserRequest.getComment());
// 设置用户id
Authentication.setAuthenticatedUserId(user.getFirst()+user.getLast()+"["+currentGroup.getName()+"]");
// 完成任务
taskService.complete(auditAddUserRequest.getTaskId(), variables);
result.setCode(ResponseCode.SUCCESS.getValue());
}catch (Exception e){
log.error(e.getMessage());
result.setCode(ResponseCode.ERROR.getValue()).setErrMsg(e.getMessage());
}
return result;
}
删除流程
该流程肯能后期需要添加审批节点或者修改审批规则,需要提前进行删除,然后再次进行部署
@ApiOperation(value ="删除流程",notes = "删除流程")
@PostMapping(value ="/deleteDeployment")
public JsonResult deployProcess(@RequestParam("deploymentId") String deploymentId) {
return procDefService.deleteDeployment(deploymentId);
}
@Override
public JsonResult deleteDeployment(String deploymentId) {
JsonResult result = new JsonResult();
LOGGER.info("deleteDeployment(deploymentId={})", deploymentId);
Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
if (deployment == null ) {
result.setCode(ResponseCode.ERROR.getValue()).setErrMsg("流程部署id:" + deploymentId + " 不存在");
}else {
repositoryService.deleteDeployment(deploymentId, true);
result.setCode(ResponseCode.SUCCESS.getValue()).setContent("流程部署id:" + deploymentId + " 删除成功");
}
return result;
}
暂停和激活流程
我们可以暂停ProcessDefinition和ProcessInstance。如果我们暂停一个流程定义ProcessDefinition,则在它暂停挂起时我们就无法创建它的实例。我们可以使用RepositoryService来做到这一点:
@Test(expected = ActivitiException.class)
public void givenDeployedProcess_whenSuspend_thenNoProcessInstance() {
// deploy the process definition
repositoryService.suspendProcessDefinitionByKey("vacationRequest");
runtimeService.startProcessInstanceByKey("vacationRequest");
}
要再次激活它,我们只需要调用其中一个repositoryService.activateProcessDefinitionXXX方法。
同样,我们可以使用RuntimeService暂停ProcessInstance
获取下一个环节列表
@ApiOperation("获取下一环节列表")
@RequestMapping(value = "/next/list", method = RequestMethod.POST)
public List nextTask(@RequestBody PvmActivityRequest request) {
log.info("nextActivity(request={})", request);
List pvmActivityList = Lists.newArrayList();
// 根据流程ID获取下一向前的节点
if (org.apache.commons.lang3.StringUtils.isNotBlank(request.getProcInsId())) {
// 根据流程实例ID查询任务
List tasks = taskService.createTaskQuery().processInstanceId(request.getProcInsId()).list();
if (!tasks.isEmpty()) {
for (Task task : tasks) {
// 根据当前任务获取当前流程的流程定义,然后根据流程定义获得所有的节点:
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) this.repositoryService)
.getDeployedProcessDefinition(task.getProcessDefinitionId());
List activitiList = processDefinition.getActivities();
// 根据任务获取当前流程执行ID,执行实例以及当前流程节点的ID:
Execution execution = this.runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();//根据ID获取执行器
String activitiId = execution.getActivityId();
// 循环activitiList 并判断出当前流程所处节点,然后得到当前节点实例,根据节点实例获取所有从当前节点出发的路径,然后根据路径获得下一个节点实例:
if (!activitiList.isEmpty()) {
for (ActivityImpl activityImpl : activitiList) {
if (activitiId.equals(activityImpl.getId())) {
List outTransitions = activityImpl.getOutgoingTransitions();// 获取从某个节点出来的所有线路
recursiveSelect(pvmActivityList, outTransitions,
ArrayUtils.toArray(ProcessConstant.ACTIVITY_TYPE_USERTASK, ProcessConstant.ACTIVITY_TYPE_ENDEVENT));
break;
}
}
}
}
}
} else if (org.apache.commons.lang3.StringUtils.isNotBlank(request.getTenantId()) && org.apache.commons.lang3.StringUtils.isNotBlank(request.getProcDefKey())) {
// 根据tenantId和流程定义key查找开始用户节点
ProcessDefinition processDefinition = this.repositoryQuery
.getLatestProcDefByKeyAndTenantId(request.getProcDefKey(), request.getTenantId());
if (processDefinition != null) {
ProcessDefinitionEntity processDefinitionEntity = this.repositoryQuery
.getProcDefEntity(processDefinition.getId());
List activitiList = processDefinitionEntity.getActivities();
if (activitiList.isEmpty()) {
// 找到开始节点
ActivityImpl startEvent = null;
for (ActivityImpl activityImpl : activitiList) {
if (ProcessConstant.ACTIVITY_TYPE_STARTEVENT.equalsIgnoreCase(
activityImpl.getProperty("type").toString())) {
startEvent = activityImpl;
break;
}
}
// 找到开始节点后的第一个用户节点
if (startEvent != null) {
List outTransitions = startEvent.getOutgoingTransitions();// 获取从某个节点出来的所有线路
recursiveSelect(pvmActivityList, outTransitions,
ProcessConstant.ACTIVITY_TYPE_USERTASK);
}
}
}
}
List list = Lists.newArrayList();
if (!pvmActivityList.isEmpty()) {
list = pvmActivityList.stream().map(activityFunction).collect(Collectors.toList());
}
return list;
}
/**
* 递归获取指定类型的节点
*
* @param pvmActivityList
* @param outTransitions
* @param types
* @return
*/
public static List recursiveSelect(List pvmActivityList,
List outTransitions, String... types) {
if (!outTransitions.isEmpty()) {
for (PvmTransition pvmTransition : outTransitions) {
// 根据流程线的condition过滤驳回的环节,如果包含back则跳过指向流程
Object conditionText = pvmTransition.getProperty("conditionText");
if (conditionText != null
&& conditionText.toString().equalsIgnoreCase(ProcessConstant.TASK_STATUS_BACK)) {
continue;
}
PvmActivity pvmActivity = pvmTransition.getDestination();
Object typeObj = pvmActivity.getProperty("type");
if (typeObj != null) {
if (ArrayUtils.contains(types, typeObj.toString())) {
pvmActivityList.add(pvmActivity);
} else {
recursiveSelect(pvmActivityList, pvmActivity.getOutgoingTransitions(), types);
}
}
}
}
return pvmActivityList;
}
获取下一环节用户列表
/**
* 获取下一环节用户列表
* @param request
* @return
*/
public List listUserByActId(ActivityUserRequest request) {
log.info("listUserByActId(request={})", request);
final List response = Lists.newArrayList();
ProcessDefinition processDefinition = this.repositoryQuery.getProcDefById(request.getProcDefId());
Preconditions.checkNotNull(processDefinition,"流程定义ID【" + request.getProcDefId() + "】不存在");
ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) ((RepositoryServiceImpl) this.repositoryService)
.getDeployedProcessDefinition(processDefinition.getId());
List activitiList = processDefinitionEntity.getActivities();
if (!activitiList.isEmpty()) {
List filterList = activitiList.stream()
.filter(activity -> request.getActivityId().equals(activity.getId()))
.collect(Collectors.toList());
Preconditions.checkNotNull(filterList,"节点ID【" + request.getActivityId() + "】不存在");
ActivityImpl activity = filterList.get(0);
ActivityBehavior activityBehavior = activity.getActivityBehavior();
// 检查是否用户任务
boolean result = activityBehavior instanceof UserTaskActivityBehavior;
if (!result) {
throw new NullPointerException(String.valueOf("节点ID【\" + request.getActivityId() + \"】非用户任务"));
}
UserTaskActivityBehavior utaBehavior = (UserTaskActivityBehavior) activityBehavior;
TaskDefinition taskDefinition = ((UserTaskActivityBehavior) utaBehavior).getTaskDefinition();
// 获取候选用户ID表达式列表
Set userIds = taskDefinition.getCandidateUserIdExpressions();
if (!userIds.isEmpty()) {
List users = userIds.stream().map(userId -> {
return userId.getExpressionText();
}).collect(Collectors.toList());
// 添加指定用户ID列表的用户
response.addAll(users.stream().map(userInfo->{
UserResponse user = new UserResponse();
user.setUserId(userInfo);
return user;
}).collect(Collectors.toList()));
// 添加指定流程变量表达式的用户
if (org.apache.commons.lang3.StringUtils.isNotBlank(request.getProcInsId())) {
users.forEach(user -> {
// 流程启动人变量
if (user.equals(ProcessConstant.PROCESS_VARIABLES_START_USER_ID)) {
String userId = this.runtimeQuery.getProcessStartUser(request.getProcInsId());
if (org.apache.commons.lang3.StringUtils.isNotBlank(userId)) {
UserResponse user1 = new UserResponse();
user1.setUserId(userId);
response.add(user1);
}
}
});
}
}
// 获取候选组ID列表
Set groupIds = taskDefinition.getCandidateGroupIdExpressions();
if (!groupIds.isEmpty()) {
List groups = groupIds.stream().map(groupId -> {
return groupId.getExpressionText();
}).collect(Collectors.toList());
// 查找所有该组的用户
groups.forEach(group -> {
GroupUserQueryRequest queryRequest = new GroupUserQueryRequest(request.getTenantId(), group);
//response.addAll(this.identityManager.listUserByGroup(queryRequest));
});
}
}
return response;
}
获取流程图图片
/**
* 流程定义Key
* 流程实例ID
* 租户主键
* 获取流程图图片
* @param request
* @return
*/
public InputStream tracePhoto(TraceRequest request) {
log.info("tracePhoto(request={})", request);
ProcessDefinition processDefinition = this.checkProcDef(request.getProcDefKey(), request.getTenantId());
List activeActivityIds = Lists.newArrayList();
if (this.runtimeService.createExecutionQuery().executionId(request.getProcInsId()).count() > 0) {
activeActivityIds = runtimeService.getActiveActivityIds(request.getProcInsId());
}
Context.setProcessEngineConfiguration(this.processEngine.getProcessEngineConfiguration());
BpmnModel bpmnModel = this.repositoryService.getBpmnModel(processDefinition.getId());
return new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png", activeActivityIds,
Collections.emptyList(), this.processEngine.getProcessEngineConfiguration().getActivityFontName(),
this.processEngine.getProcessEngineConfiguration().getLabelFontName(),
this.processEngine.getProcessEngineConfiguration().getAnnotationFontName(), null, 1.0);
}
/* 检查流程定义 */
private ProcessDefinition checkProcDef(String procDefKey, String tenantId) {
ProcessDefinition processDefinition = this.repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(procDefKey).processDefinitionTenantId(tenantId).latestVersion().active().singleResult();
if (processDefinition == null) {
throw new NullPointerException(String.valueOf("流程定义Key:"+ procDefKey + " 不存在"));
}
return processDefinition;
}
获取审批意见列表
/**
* 获取审批意见列表
* @param procInsId
* @return
*/
@Override
public List listComment(String procInsId) {
log.info("listComment(procInsId={})", procInsId);
List responseList = Lists.newArrayList();
List comments = this.taskService.getProcessInstanceComments(procInsId,
ProcessConstant.COMMENT_TYPE_COMMENT);
if (!comments.isEmpty()) {
comments.forEach(comment -> {
CommentResponse commentResponse = new CommentResponse();
CommentEntity commentEntity = (CommentEntity) comment;
BeanUtils.copyProperties(commentEntity, commentResponse);
// 没有任务ID则为启动流程,默认状态为true(同意)
if (org.apache.commons.lang3.StringUtils.isNotBlank(comment.getTaskId())) {
HistoricTaskInstance historicTaskInstance = this.historyService.createHistoricTaskInstanceQuery().processInstanceId(procInsId)
.taskId(comment.getTaskId()).includeProcessVariables().includeTaskLocalVariables()
.finished().singleResult();
if (historicTaskInstance != null) {
// 有任务ID,则判断任务状态
String status = historicTaskInstance.getTaskLocalVariables().get(ProcessConstant.TASK_VARIABLES_DEAL_STATU).toString();
if (ProcessConstant.TASK_STATUS_BACK.equals(status)) {
commentResponse.setStatus(false);
}
// 设置任务名称
commentResponse.setTaskName(historicTaskInstance.getName());
}
}
responseList.add(commentResponse);
});
}
// 按最新审批时间排序
if (!responseList.isEmpty()) {
Collections.sort(responseList, Comparator.comparing(CommentResponse::getTime).reversed());
}
// 获取当前处理人 和 当前节点
ProcessInstance processInstance = this.runtimeQuery.getActiveProcInst(procInsId);
if (processInstance != null) {
CommentResponse commentResponse = new CommentResponse();
commentResponse.setUserId(ObjectUtils.nullSafeToString(
processInstance.getProcessVariables().get(ProcessConstant.PROCESS_VARIABLES_CLAIM)));
//全局变量 map
ProcessDefinitionCache.setRepositoryService(repositoryService);
commentResponse.setTaskName(ProcessDefinitionCache.getActivityName(processInstance.getProcessDefinitionId(),
processInstance.getActivityId()));
commentResponse.setStatus(null);
responseList.add(0, commentResponse);
}
return responseList;
}
结束语
到目前位置,Activiti主要的功能已经完成的差不多了,后面还有一些激活流程,或者将流程模板挂起,后续再补充上。