目录:
Activiti 5 实用性代码解析:
针对***-activiti系统进行开发,demo为实用mysql进行测试,实际需要迁移至神通数据库进行
笔者已经整理了一份对应的jar包,可以直接引用:
activiti适配神通数据库jar包
有兴趣的可以研究一下源码:
activiti5.22.0源码下载
mysql数据源配置:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/aceace?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=GMT%2B8
username: ***
password: ***
神通数据源配置:
datasource:
driver-class-name: com.oscar.Driver
url: jdbc:oscar://127.0.0.1:2003/***
username: system
password: ***
JDBC:
JDBC下载链接
1.依赖引入以及数据配置:
1.1引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>***-modules</artifactId>
<groupId>com.***</groupId>
<version>2.4.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>***-act</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<activiti.version>5.22.0</activiti.version>
</properties>
<dependencies>
<!-- SpringCloud Ailibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Ailibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud Ailibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.12</version>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.fox.version}</version>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!-- *** Common DataSource -->
<dependency>
<groupId>com.***</groupId>
<artifactId>***-common-datasource</artifactId>
</dependency>
<!-- *** Common DataScope -->
<dependency>
<groupId>com.***</groupId>
<artifactId>***-common-datascope</artifactId>
</dependency>
<!-- *** Common Log -->
<dependency>
<groupId>com.***</groupId>
<artifactId>***-common-log</artifactId>
</dependency>
<!-- *** Common Swagger -->
<dependency>
<groupId>com.***</groupId>
<artifactId>***-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>com.***</groupId>
<artifactId>***-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.***</groupId>
<artifactId>***-api-file</artifactId>
<version>2.4.0</version>
<scope>compile</scope>
</dependency>
<!--activiti begin-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-oscar-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-oscar-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-modeler</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-diagram-rest</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.7.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.核心源码分析:
2.1 activiti controller common入口
2.1.1CommonActiviteController.initiateAudit启动工作流引擎
/**
* @author lijiaheng
* @Description 启动工作流引擎
* @Date 2022/6/9 17:54
* @Param [initiateAuditParamerterVo]
* @return
*/
@Operation(summary = "启动工作流引擎")
@PostMapping("/initiateAudit")
@ResponseBody
public ResponseMessage initiateAudit(@Valid @RequestBody InitiateAuditParamerterVo initiateAuditParamerterVo) throws IOException {
log.info("启动工作流引擎参数:" + JSON.toJSONString(initiateAuditParamerterVo));
return activitiService.initiateAudit(initiateAuditParamerterVo);
}
activitiService.initiateAudit(initiateAuditParamerterVo);
@Override
public AjaxResult initiateAudit(CommonActStartRequest request) {
HashMap<String, Object> deployment = new HashMap<>();
ArrayList<HashMap<String, String>> hashMaps = new ArrayList<>();
HashMap<String, Object> variables = new HashMap<>();
//添加下一节点审批人
List<String> nextUserIds = new ArrayList<>();
//获取当前人的所属机构和审批类型id
// LoginUser loginUser = tokenService.getLoginUser();
// SysUser sysUser = loginUser.getSysUser();
// String orgId = sysUser.getOrgId();
String orgId = "122";
String bussNameCode = request.getBussNameCode();
ActGroupDetailRequest actGroupDetailRequest = new ActGroupDetailRequest();
actGroupDetailRequest.setOrgId(orgId);
actGroupDetailRequest.setApplyTypeId(Long.valueOf(bussNameCode));
List<SysGroupDetailResponse> sysGroupDetailResponses = actGroupService.findBy(actGroupDetailRequest);
List<SysGroupDetailResponse> sortedSysGroupDetailResponses = sysGroupDetailResponses.stream().sorted(Comparator.comparing(SysGroupDetailResponse::getApplyLeave)).collect(Collectors.toList());
for (int i = 0; i < sortedSysGroupDetailResponses.size(); i++) {
variables.put("applyLeave" + (i + 1), sortedSysGroupDetailResponses.get(i));
}
variables.put("dealType", sortedSysGroupDetailResponses.size());
variables.put("nowLeave",0);
SysGroupDetailResponse applyLeave1 = (SysGroupDetailResponse) variables.get("applyLeave1");
String applyOrgId = applyLeave1.getApplyOrgId();
Long applyRoleId = applyLeave1.getApplyRoleId();
OrgAndRoleVo orgAndRoleVo = new OrgAndRoleVo();
orgAndRoleVo.setOrgId(applyOrgId);
orgAndRoleVo.setRoleId(applyRoleId + "");
R<List<String>> nextUserIdResponse = remoteUserService.findUserByOrgAndRole(orgAndRoleVo);
if (200 != nextUserIdResponse.getCode() || CollectionUtils.isEmpty(nextUserIdResponse.getData())) {
// return AjaxResult.error("通过机构和角色查询用户ids失败:"+nextUserIdResponse.getMsg());
}
nextUserIds = nextUserIdResponse.getData();
nextUserIds.add("105");
nextUserIds.add("107");
nextUserIds.add("108");
nextUserIds.add("109");
variables.put("inputUser", nextUserIds);
if (null != variables.get("inputUser")) {
variables.put("inputUser", variables.get("inputUser"));
}
//拿到模型ID todo:先写死
// LoginUser loginUser = tokenService.getLoginUser();
// SysUser sysUser = loginUser.getSysUser();
// String orgId = sysUser.getOrgId();
// QueryWrapper<SysOrgActDO> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("ORG_ID", orgId);
// queryWrapper.eq("DICT_VALUE", request.getExamineType());
// queryWrapper.last("limit 1");
// SysOrgActDO sysOrgActDo = sysOrgActMapper.selectOne(queryWrapper);
// String examineId = sysOrgActDo.getModelId();
String examineId = "5e8094ac44e9450694cd1b142e8bc7b6";
//发布自定义流程
deployment.put("examineId", examineId);
log.info("模型ID:{}", examineId);
try {
AjaxResult bodeployment = this.deploy(deployment);
if ((int) bodeployment.get("code") != 200) {
return AjaxResult.error("发布自定义流程错误");
}
} catch (IOException e) {
e.printStackTrace();
return AjaxResult.error("发布自定义流程错误");
}
//调用activiti 获取审批流详情
List<Model> modelsList = repositoryService.createModelQuery().modelId(examineId).orderByCreateTime().desc().list();
if (null == modelsList) {
return null;
}
String bussId = modelsList.get(0).getKey();
// variables.put("startUserId", request.getStartUserId());
variables.put("bussId", bussId);
HashMap<String, Object> businessParameters = request.getBusinessParameters();
if (null != businessParameters) {
for (String param : businessParameters.keySet()) {
variables.put(param, businessParameters.get(param));
}
}
// variables.put("bussName", request.getStartUser() + "发起 " + sysDictData.getDictLabel() + "流程");
// variables.put("bussName", request.getStartUser() + "发起 " + "流程");
// variables.put("bussNameCode", request.getBussNameCode());
//调用activiti 启动流程
//获取发起人信息
System.out.println(variables);
//根据类型获取模型ID
String key = variables.get("bussId").toString();
//拿到bussId
//获取机构ID 角色ID 审批流的组 用户ID
FlowAssignee.setUserId(nextUserIds);
//发布为自定义流程
variables.put("startUserId", "1");
if (null != variables.get("startUserId")) {
identityService.setAuthenticatedUserId(variables.get("startUserId").toString());
}
if (null != variables.get("startUserId")) {
//todo:需要修改
identityService.setUserInfo("1", "startUnitId", "1");
}
if (null != variables.get("inputUser")) {
variables.put("inputUser", variables.get("inputUser"));
}
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, variables);
log.info("启动一个流程实例,pi:{}", processInstance.getId());
runtimeMapper.updateValue(processInstance.getId(),((int)variables.get("nowLeave"))+1,"nowLeave");
return AjaxResult.success(processInstance.getId());
}
2.1.2核心代码:
通过工作流model部署一个工作流
this.deploy(deployment);
通过仓库接口查询到部署的工作流model信息
List<Model> modelsList = repositoryService.createModelQuery().modelId(examineId).orderByCreateTime().desc().list();
通过运行中接口启动对应发布的实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, variables);
其中
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, variables);
*variables 可以进行参数携带
CommonActiviteController.processFlow流程流转
/**
* @author lijiaheng
* @Description 流程流转
* @Date 2022/6/9 18:09
* @Param [processFlowParamerterVo]
* @return
*/
@ApiOperation(value = "流程流转")
@PostMapping("/processFlow")
@ResponseBody
public AjaxResult processFlow(@RequestBody @Valid ProcessFlowParamerterVo processFlowParamerterVo) throws IOException {
return activitiService.processFlow(processFlowParamerterVo);
}
activitiService.processFlow(processFlowParamerterVo);
@Override
public AjaxResult processFlow(ProcessFlowParamerterVo processFlowParamerterVo) {
Map<Object, Object> params = new HashMap<>();
List<String> nextUserIds = new ArrayList<>();
String userId = processFlowParamerterVo.getApplyId();
params.put("aciviteId", processFlowParamerterVo.getAciviteId());
params.put("dealUserId", userId);
params.put("nextUserId", nextUserIds);
params.put("applyStatus",processFlowParamerterVo.getApplyStatus());
//拿到实例ID 查询当前实例执行到的任务ID
String aciviteId = params.get("aciviteId").toString();
//todo 判断流程是否结束
ProcessInstance rpi = ProcessEngines.getDefaultProcessEngine().getRuntimeService()//
.createProcessInstanceQuery()//创建流程实例查询对象
.processInstanceId(aciviteId)
.singleResult();
if (null == rpi) {
return AjaxResult.error("流程已结束");
}
List<Map<String, Object>> list = new ArrayList<>();
MysHistoryPagerReqParDto mysHistoryPagerReqParDto = new MysHistoryPagerReqParDto();
mysHistoryPagerReqParDto.setAciviteId(new String[]{aciviteId});
//拿到实例对象
List<Map<String, Object>> process = historyInfoService.process(mysHistoryPagerReqParDto);
historyInfoService.setVariables(list, process);
Map<String, Object> stringObjectMap = process.get(0);
if (process.size() == 0) {
return AjaxResult.error("找不到此实例!");
}
ArrayList approvalProcess = (ArrayList) process.get(0).get("approvalProcess");
HttpApprovalHistory httpApprovalHistory = (HttpApprovalHistory) approvalProcess.get(approvalProcess.size() - 1);
//任务ID
String taskId = httpApprovalHistory.getTaskId();
System.out.println("taskId++++++++++++++++++++++++++++++++++++++++++++++++:" + taskId);
log.info("审核参数:{}", JSON.toJSONString(params));
//判断是否有权限
LoginUser loginUser = tokenService.getLoginUser();
SysUser sysUser = loginUser.getSysUser();
Long applyUserId = sysUser.getUserId();
SysGroupDetailResponse sysGroupDetailResponse = (SysGroupDetailResponse)stringObjectMap.get("applyLeave" + (int) stringObjectMap.get("nowLeave"));
String applyOrgId = sysGroupDetailResponse.getApplyOrgId();
Long applyRoleId = sysGroupDetailResponse.getApplyRoleId();
OrgAndRoleVo orgAndRoleVo = new OrgAndRoleVo();
orgAndRoleVo.setOrgId(applyOrgId);
orgAndRoleVo.setRoleId(applyRoleId+"");
R<List<String>> nextUserIdResponse = remoteUserService.findUserByOrgAndRole(orgAndRoleVo);
if (200 != nextUserIdResponse.getCode() || CollectionUtils.isEmpty(nextUserIdResponse.getData())) {
// return AjaxResult.error("通过机构和角色查询用户ids失败:"+nextUserIdResponse.getMsg());
}
List<String> userIds = nextUserIdResponse.getData();
if (!userIds.contains(applyUserId)){
return AjaxResult.error("当前人无权限审批");
}
log.info("审核状态 : 任务ID {} , 状态: {} ", taskId, Integer.valueOf(params.get("dealType").toString()) == 0 ? "审核通过" : "审核不通过");
//审核通过
if (0 == Integer.valueOf(params.get("dealType").toString())) {
activitiService.completeTask(taskId, params);
runtimeMapper.updateValue(aciviteId,((int)stringObjectMap.get("nowLeave"))+1,"nowLeave");
}
//审核不通过
if (0 != Integer.valueOf(params.get("dealType").toString())) {//驳回
try {
if (!activitiService.rejectTask(taskId, params)) {
return AjaxResult.error("处理失败!");
}
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("处理失败!");
}
}
runtimeMapper.updateValue(aciviteId,0,"nowLeave");
return AjaxResult.success("处理成功!");
}
核心代码:
通过历史流程信息接口查询实例对象相关历史数据(对activiti历史接口的封装)
//拿到实例对象,通过数据库直接查询
List<Map<String, Object>> process = historyInfoService.process(mysHistoryPagerReqParDto);
eg: sql
sql:
<select id="selectMyProcessStarted" resultType="java.util.Map"
parameterType="com.***.act.model.MysHistoryPagerReqParDto">
select
*
from act_hi_procinst #activiti历史核心表
<where>
PROC_INST_ID_ in
(select PROC_INST_ID_ from act_hi_varinst where NAME_='startUserId'
<if test="userId != null and userId != ''">and TEXT_=#{userId}</if>
)
<choose>
<when test="aciviteId != null and aciviteId != ''">
and PROC_INST_ID_ in
<foreach collection="aciviteId" item="merId" open="(" separator="," close=")" index="index">
#{merId}
</foreach>
</when>
</choose>
order by START_TIME_ desc
<choose>
<when test="lastId != null and lastId>0">
limit #{pageSize}
</when>
<otherwise>
limit #{offSet},#{pageSize}
</otherwise>
</choose>
</where>
</select>
组装核心数据
historyInfoService.setVariables(list, process);
原理:
通过历史接口查询(activiti),原理也是查询历史信息相关表,并关联task表等
List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().processInstanceId((String) hipro.get("PROC_INST_ID_")).list();
variables此对象可以进行获取我们上文提到的 *variables 可以进行参数携带
HttpApprovalHistory
此类是对于通过historyService接口查询的数据进一步封装
eg:
{
"msg": "操作成功",
"code": 200,
"data": {
"PROC_INST_ID_": "d8db5c4d79ff4583a00b824061c66178",
"bussNameCode": "3",
"actStatus": "1",
"ID_": "d8db5c4d79ff4583a00b824061c66178",
"inputUser": [
"105",
"107",
"108",
"109"
],
"mixApplyLeave": 10,
"START_ACT_ID_": "sid-2FB3AD4B-AD21-4EB0-B6D6-3B618BB27DEE",
"bussId": "***-act",
"applyLeave1": {
"groupId": 5,
"orgId": "122",
"applyTypeId": 3,
"applyLeave": 1,
"applyOrgId": "1",
"applyRoleId": 12
},
"applyLeave2": {
"groupId": 4,
"orgId": "122",
"applyTypeId": 3,
"applyLeave": 2,
"applyOrgId": "1",
"applyRoleId": 12
},
"applyLeave3": {
"groupId": 6,
"orgId": "122",
"applyTypeId": 3,
"applyLeave": 3,
"applyOrgId": "1",
"applyRoleId": 12
},
"startUserId": "1",
"applyLeave4": {
"groupId": 1532266149058842624,
"orgId": "122",
"applyTypeId": 3,
"applyLeave": 4,
"applyOrgId": "1",
"applyRoleId": 12
},
"startUserStr": "1",
"applyLeave5": {
"groupId": 7,
"orgId": "122",
"applyTypeId": 3,
"applyLeave": 5,
"applyOrgId": "1",
"applyRoleId": 12
},
"PROC_DEF_ID_": "***-act:63:7f7633a6a863416a9a05dec7665dc465",
"START_USER_ID_": "1",
"START_TIME_": "2022-06-09 10:28:01",
"approvalProcess": [
{
"approvalOpinion": null,
"approvalStatus": null,
"approvalUserName": null,
"approvalUserID": "[105, 107, 108, 109]",
"nextPpprovalUserID": null,
"nextPpprovalRoleID": null,
"deptName": null,
"approvalTime": null,
"agree": "1",
"startTime": 1654741681945,
"endTime": null,
"durationInMillis": 0,
"durationInMillisStr": "0 天 0 小时 0 分钟 0 秒 ",
"taskId": "90b98b18fd15499b8df44297a0d18c15",
"activityId": "one",
"activityName": "第一级审批人"
}
]
}
}
注意:也可以通过taskService任务接口将需要携带的参数放入表单中,通过TaskFormdata进行解析。适配动态表单,本文不再详细阐述
任务流转实现activiti核心类:
核心代码:
此代码为流程流转代码,原理是通过taskService任务接口进行参数的流转和确认以及流转至结束
taskService.addComment(taskId, processInstancesId, params.get("dealType") + ":" + params.get("dealReason"));
taskService.complete(taskId, variables);
流程跳转实现核心代码:
//这种方式是通过 重写命令
public void jumpEndActivity(String executionId, String targetActId, String reason) {
((RuntimeServiceImpl) runtimeService).getCommandExecutor().execute(new Command<Object>() {
@Override
public Object execute(CommandContext commandContext) {
ExecutionEntity execution = commandContext.getExecutionEntityManager().findExecutionById(executionId);
execution.destroyScope(reason); //
ProcessDefinitionImpl processDefinition = execution.getProcessDefinition();
ActivityImpl findActivity = processDefinition.findActivity(targetActId);
execution.executeActivity(findActivity);
return execution;
}
});
log.info("自由跳转至节点:{}-----完成", targetActId);
}
CommonActiviteController.findByActId通过activitiId查询流程需要那些人审批
/**
* @author lijiaheng
* @Description 通过activitiId查询流程需要那些人审批
* @Date 2022/6/9 17:02
* @Param [activiId]
* @return
*/
@GetMapping(value = "/findByActId/{activiId}")
public AjaxResult findByActId(@PathVariable String activiId) {
return activitiService.findByActId(activiId);
}
activitiService.findByActId(String activiId)
@Override
public AjaxResult findByActId(String activiId) {
List<Map<String, Object>> list = new ArrayList<>();
//查询实例
MysHistoryPagerReqParDto mysHistoryPagerReqParDto = new MysHistoryPagerReqParDto();
mysHistoryPagerReqParDto.setAciviteId(new String[]{activiId});
//拿到实例对象
List<Map<String, Object>> process = historyInfoService.process(mysHistoryPagerReqParDto);
historyInfoService.setVariables(list, process);
Map<String, Object> stringObjectMap = process.get(0);
//actStatus 审批流 状态 0 完成审批 1运行中
if("0".equals(stringObjectMap.get("actStatus"))){
return AjaxResult.error("流程已结束");
}
List<String> inputUsers = (List<String>) stringObjectMap.get("inputUser");
return AjaxResult.success(inputUsers);
此代码就是利用了historyServic接口的实例代码
了解了activiti可携带参数后那么我们可以做的事情就比较多了,不仅可以将我们业务需要的参数放入,也可已将流程相关的参数进行修改
eg:
通过实现RuntimeMapper
int updateValue(@Param("actId") String actId,
@Param("nowLeave") Integer nowLeave,
@Param("name") String name);
可以动态的修改我们携带的参数,例如下面是对nowLeave字段进行修改
<update id="updateValue">
update ACT_RU_VARIABLE
set
TEXT_ = #{nowLeave}
where
PROC_INST_ID_ = #{actId}
and
NAME_ = #{name}
</update>
CommonActiviteController.findByUserId 通过userId查询流程需要那些人审批
核心思路:
通过流程变量记录的ACT_RU_TASK表进行筛选查询,此表为运行时记录表,记录当前流程运行的实例流程,流程结束后此表会通过activiti引擎关联删除。因此,可以作为判断当前人待办处理数据的地方。
/**
* @author lijiaheng
* @Description 通过userId查询流程需要那些人审批
* @Date 2022/6/9 17:12
* @Param []
* @return
*/
@GetMapping(value = "/findByUserId")
public AjaxResult findByUserId() {
return activitiService.findByUserId();
}
activitiService.findByUserId
@Override
public AjaxResult findByUserId(String businessCode, Integer pageNum, Integer pageSize) {
if (StringUtils.isEmpty(businessCode)) {
if (Objects.isNull(pageNum) || Objects.isNull(pageSize)) {
pageNum = 0;
pageSize = 10;
}
}
LoginUser loginUser = tokenService.getLoginUser();
SysUser sysUser = loginUser.getSysUser();
String userId = sysUser.getUserId() + "";
// String userId = "136";
List<String> pidList = new ArrayList<>();
List<ActRuTask> usersForRuTask = actRuTaskMapper.selectAllForUser();
if (Objects.isNull(usersForRuTask)) {
return AjaxResult.success();
}
for (ActRuTask actRuTask : usersForRuTask) {
List<String> list = JSON.parseArray(actRuTask.getAssignee(), String.class);
if (CollectionUtils.isEmpty(list)) {
continue;
}
if (list.contains(userId)) {
pidList.add(actRuTask.getProcInstId());
}
}
if(CollectionUtils.isEmpty(pidList)){
return AjaxResult.success();
}
// pidList = new ArrayList<>();
// pidList.add("65a6ba4b393e48a3b3e925831f294dc8");
// pidList.add("6fef01e3c28d46f28abd19fdd0b4d52c");
// pidList.add("6fef01e3c28d46f28abd19fdd0b4d52c");
Long totalNum = Long.valueOf(pidList.size() + "");
List pageList = getPageList(pidList, pageNum, pageSize);
String[] activiteIds = (String[]) pidList.toArray(new String[pageList.size()]);
if (null == activiteIds || 0 == activiteIds.length) {
return AjaxResult.success();
}
MysHistoryPagerReqParDto mysHistoryPagerReqParDto = new MysHistoryPagerReqParDto();
mysHistoryPagerReqParDto.setAciviteId(activiteIds);
//拿到实例对象
List<Map<String, Object>> process = historyInfoService.process(mysHistoryPagerReqParDto);
List<FindByUserIdReponseVo> vos = historyInfoService.nowVariables(process);
if (StringUtils.isEmpty(businessCode)) {
FindByUserIdTotalNumReponseVo responseVo = new FindByUserIdTotalNumReponseVo();
responseVo.setTotalNum(totalNum);
responseVo.setFindByUserIdReponseVos(vos);
return AjaxResult.success("成功", responseVo);
}
List<FindByUserIdReponseVo> unPageList = vos.stream().filter(p -> businessCode.equals(p.getBusinessCode())).collect(Collectors.toList());
log.info("unPageList=========================={}", unPageList);
// Map<String, List<FindByUserIdReponseVo>> listMap = vos.stream().collect(Collectors.groupingBy(p -> StringUtils.isEmpty(p.getBusinessCode()) ? "0000" : p.getBusinessCode(), Collectors.toList()));
// List<FindByUserIdReponseVo> groupVos = listMap.get(businessCode);
// if (CollectionUtils.isEmpty(groupVos)) {
// return AjaxResult.success();
// }
return AjaxResult.success("成功", unPageList);
}
CommonActiviteController.commitUpdate退回提交
/**
* @return
* @author lijiaheng
* @Description 退回提交
* @Date 2022/6/17 19:10
* @Param [commitUpdateRequestVo]
*/
@ApiOperation(value = "退回提交")
@PostMapping(value = "/commitUpdate")
public AjaxResult commitUpdate(@RequestBody CommitUpdateRequestVo commitUpdateRequestVo) {
return activitiService.commitUpdate(commitUpdateRequestVo);
}
activitiService.commitUpdate
@Override
public AjaxResult commitUpdate(CommitUpdateRequestVo commitUpdateRequestVo) {
if (StringUtils.isEmpty(commitUpdateRequestVo.getBusinessValue())) {
return AjaxResult.success();
}
String actId = commitUpdateRequestVo.getActId();
// String actId = "93b0aaa899304ee9b10a420a5b7edac9";
//todo 判断流程是否结束
ProcessInstance rpi = ProcessEngines.getDefaultProcessEngine().getRuntimeService()//
.createProcessInstanceQuery()//创建流程实例查询对象
.processInstanceId(actId)
.singleResult();
if (null == rpi) {
return AjaxResult.error("流程已结束");
}
Map<String, Object> stringObjectMap = this.auditOpinion(actId);
if (CollectionUtils.isEmpty(stringObjectMap)) {
return AjaxResult.error("未查询到历史流转信息");
}
List<HttpApprovalHistory> approvalProcess = (List<HttpApprovalHistory>) stringObjectMap.get("approvalProcess");
HttpApprovalHistory httpApprovalHistory = approvalProcess.get(approvalProcess.size() - 1);
//任务ID
String taskId = httpApprovalHistory.getTaskId();
log.info("taskId++++++++++++++++++++++++++++++++++++++++++++++++{}", taskId);
taskService.setVariable(taskId, "businessValue", commitUpdateRequestVo.getBusinessValue());
SysGroupDetailResponse sysGroupDetailResponse = (SysGroupDetailResponse) stringObjectMap.get("applyLeave1");
OrgAndRoleVo orgAndRoleVo = new OrgAndRoleVo();
orgAndRoleVo.setOrgId(sysGroupDetailResponse.getApplyOrgId());
orgAndRoleVo.setRoleId(sysGroupDetailResponse.getApplyRoleId());
R<List<String>> nextUserIdResponse = remoteUserService.findUserByOrgAndRole(orgAndRoleVo);
if (200 != nextUserIdResponse.getCode() || CollectionUtils.isEmpty(nextUserIdResponse.getData())) {
return AjaxResult.error("通过机构和角色查询用户ids失败:" + nextUserIdResponse.getMsg());
}
int i = actRuTaskMapper.updateByActId(commitUpdateRequestVo.getActId(), nextUserIdResponse.getData().toString());
if (i == 0) {
log.info("ACT_RU_TASK表更新失败");
}
return AjaxResult.success();
}
核心思路:
通过工作流本身的流程流转,跳动至任意一个已经定义的审批节点上,此处为调转到原点,并且需要第一个审批人查询不到当前此任务的待审批。
CommonActiviteController.endProcess结束流程 管理源权限
/**
* @author lijiaheng
* @Description 结束流程 管理源权限
* @Date 2022/6/18 18:09
* @Param [activiId, request]
* @return
*/
@ApiOperation(value = "结束流程")
@GetMapping("/endProcess")
@ResponseBody
public AjaxResult endProcess(@RequestParam("actId") String actId) {
return activitiService.endProcess(actId);
}
activitiService.endProcess
@Override
public AjaxResult endProcess(String activiId) {
try {
//todo 判断流程是否结束
ProcessInstance rpi = ProcessEngines.getDefaultProcessEngine().getRuntimeService()//
.createProcessInstanceQuery()//创建流程实例查询对象
.processInstanceId(activiId)
.singleResult();
if (null == rpi) {
return AjaxResult.error("流程已结束");
}
ProcessEngines.getDefaultProcessEngine().getRuntimeService().deleteProcessInstance(activiId, "【" + activiId + "】流程停止!!!");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("失败");
}
return AjaxResult.success("成功");
}
流程停止(删除)核心代码:
ProcessEngines.getDefaultProcessEngine().getRuntimeService().deleteProcessInstance(activiId, "【" + activiId + "】流程停止!!!");
补充1:
查询流程变量方法:
List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().processInstanceId((String) hipro.get("PROC_INST_ID_")).list();
补充2:
监听实现:
MyProcessExecutionListener
/**
* 流程实例监听类
*
* @auther: Ace Lee
* @date: 2019/3/8 11:57
*/
@Component("MyProcessExecutionListener")
@Slf4j
public class MyProcessExecutionListener implements ExecutionListener {
// @Resource
// private ExecuteLogService executeLogService;
@Resource
private HistoryInfoService historyInfoService;
// acbdc22dc1a44ec297765b8dbdb76cf0
@Override
public void notify(DelegateExecution execution) throws Exception {
String eventName = execution.getEventName();
if ("start".equals(eventName)) {
log.info("==================start==================");
} else if ("end".equals(eventName)) {
String processInstancesId = execution.getId();
// //3 撤销流程
// String dealType = "3";
// if (null != execution.getVariableInstances().get("dealType")) {
// if (null != execution.getVariableInstances().get("dealType").getCachedValue())
// dealType = execution.getVariableInstances().get("dealType").getCachedValue().toString();
// }
// Map<String, VariableInstance> variableInstances = execution.getVariableInstances();
// String dealReason = "撤销申请";
// if (null != execution.getVariableInstances().get("dealReason")) {
// if (null != execution.getVariableInstances().get("dealReason").getCachedValue())
// dealReason = execution.getVariableInstances().get("dealReason").getCachedValue().toString();
// } else {
// HashMap<String, String> stringStringHashMap = new HashMap<>();
// stringStringHashMap.put("dealType", "3");
// execution.setVariables(stringStringHashMap);
//
// }
MysHistoryPagerReqParDto mysHistoryPagerReqParDto = new MysHistoryPagerReqParDto();
mysHistoryPagerReqParDto.setAciviteId(new String[]{processInstancesId});
//拿到实例对象
HistoryInfoService executeLogService = SpringUtils.getBean("historyInfoServiceImpl");
Class sysLogininforService = SpringUtils.getDependentBeans("historyInfoServiceImpl").getClass();
Method[] methods = sysLogininforService.getMethods();
List<Map<String, Object>> process = new ArrayList<>();
process = executeLogService.process(mysHistoryPagerReqParDto);
List<Map<String, Object>> list = new ArrayList<>();
executeLogService.setVariables(list, process);
log.info("取最后一个操作人的信息:{}", process);
String bussType = getStringObject(process.get(0).get("bussType"));
String dealUserId = getStringObject(process.get(0).get("startUserId"));
if (null != process.get(0).get("dealUserId")) {
dealUserId = process.get(0).get("dealUserId").toString();
}
String bussName = getStringObject(process.get(0).get("bussName"));
String bussNameCode = getStringObject(process.get(0).get("bussNameCode"));
Object ErpMsgBusType = getStringObject(process.get(0).get("ErpMsgBusType"));
String tenantId = getStringObject(process.get(0).get("TENANT_ID_"));
String applyCode = getStringObject(process.get(0).get("applyCode"));
System.out.println(bussType);
log.info("----------------------------------------------------------------");
log.info("完成的任务类型:{}", bussType);
log.info("完成任务:实例ID:{} , 最后审核人:{}", processInstancesId, dealUserId);
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("aciviteId", processInstancesId);
// stringObjectHashMap.put("dealReason", dealReason);
// stringObjectHashMap.put("dealType", dealType);
stringObjectHashMap.put("bussType", bussType);
stringObjectHashMap.put("operator", dealUserId);
stringObjectHashMap.put("bussName", bussName);
stringObjectHashMap.put("bussNameCode", bussNameCode);
stringObjectHashMap.put("tenantId", tenantId);
stringObjectHashMap.put("applyCode", applyCode);
stringObjectHashMap.put("ErpMsgBusType", ErpMsgBusType);
log.info(new JSONObject(stringObjectHashMap).toString());
// productionManagementClient.auditCallback(stringObjectHashMap);
log.info("==================end==================");
} else if ("take".equals(eventName)) {
System.out.println("take=========");
}
}
String getStringObject(Object object) {
if (null == object) {
return "";
}
return object.toString();
}
}
核心代码分析:
通过实现 ExecutionListener接口,其中可以监控到下述常量对应的流程状态(start,end,take),通过DelegateExecution对象进行获取。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.activiti.engine.delegate;
import java.io.Serializable;
public interface ExecutionListener extends Serializable {
String EVENTNAME_START = "start";
String EVENTNAME_END = "end";
String EVENTNAME_TAKE = "take";
void notify(DelegateExecution var1) throws Exception;
}
MyTaskCompletedListener
/**
* 节点任务完成监听类
*
* @auther: Ace Lee
* @date: 2019/3/8 11:57
*/
@Component
@Slf4j
public class MyTaskCompletedListener implements TaskListener {
@Autowired
protected IdentityService identityService;
//监听任务的事件
@Override
public void notify(DelegateTask delegateTask) {
//判断是否有组
if (null!= FlowAssignee.getGroupId() && FlowAssignee.getGroupId().size()>0) {
//删除人
if (null != delegateTask.getAssignee()) {
delegateTask.deleteCandidateUser(delegateTask.getAssignee());
}
for (IdentityLink candidate : delegateTask.getCandidates()) {
delegateTask.deleteCandidateGroup(candidate.getGroupId());
}
delegateTask.addCandidateGroups(FlowAssignee.getGroupId());
}
//判断是否有人
if (null!=FlowAssignee.getUserId() && FlowAssignee.getUserId().size()>0) {
//删除组
if (delegateTask.getCandidates().size() > 0) {
for (IdentityLink candidate : delegateTask.getCandidates()) {
delegateTask.deleteCandidateGroup(candidate.getGroupId());
}
}
if (null != delegateTask.getAssignee()) {
String assignee = delegateTask.getAssignee();
List<String> list = JSON.parseArray(assignee, String.class);
list.stream().forEach(e->{
delegateTask.deleteCandidateUser(e);
});
}
delegateTask.addCandidateUsers(FlowAssignee.getUserId());
}
//用完初始化
FlowAssignee.setGroupId(null);
FlowAssignee.setUserId(null);
Set<IdentityLink> candidates = delegateTask.getCandidates();
Set<String> groupIdList = new HashSet<>();
Set<String> userIdList = new HashSet<>();
String name = delegateTask.getName();
for (IdentityLink candidate : candidates) {
if (StringUtils.isNotBlank(candidate.getGroupId())) {
groupIdList.add(candidate.getGroupId());
}
if (StringUtils.isNotBlank(candidate.getUserId())) {
userIdList.add(candidate.getUserId());
}
}
TaskService taskService = (TaskService) SpringUtils.getBean("taskService");
HistoryService historyService = (HistoryService) SpringUtils.getBean("historyService");
ProcessEngine processEngine = (ProcessEngine) SpringUtils.getBean("processEngine");
List<Task> input = taskService.createTaskQuery()
.taskCandidateOrAssigned("1")
.taskDefinitionKey("admin")
.orderByTaskCreateTime().desc().list();
int size = input.size();
log.info("task--------"+size);
Task task = null;
TaskQuery taskQuery = taskService.createTaskQuery().taskId(delegateTask.getId()).active();
List<Task> todoList = taskQuery.list();
for(Task tmp : todoList){
if(tmp.getProcessInstanceId().equals(delegateTask.getExecution().getProcessInstanceId())){
task = tmp;
break;
}
}
// TaskQuery taskQuery1 = taskService.createTaskQuery().active().taskId(delegateTask.getId()).processDefinitionKey("process");
// //设置流程实例id
// taskQuery1.processInstanceId(delegateTask.getExecution().getProcessInstanceId());
//
// Task task = taskQuery1.singleResult();
//
// taskService.complete(task.getId());
String tenantId = delegateTask.getTenantId();
log.info("系统: "+tenantId);
if (groupIdList.size() > 0) {
log.info("实例ID " + delegateTask.getExecution().getProcessInstanceId() + ", 任务id :"+delegateTask.getId()+"一个任务[" + name + "]被创建了,由组" + groupIdList.toString() + "负责办理");
}
if (userIdList.size() > 0) {
log.info("实例ID " + delegateTask.getExecution().getProcessInstanceId() + ",任务id: "+delegateTask.getId()+"一个任务[" + name + "]被创建了,由" + userIdList.toString() + "负责办理");
}
}
// @Override
// public void notify(DelegateExecution delegateExecution) throws Exception {
// System.out.println(delegateExecution);
// log.info("实例ID :【" + delegateExecution.getProcessInstanceId() + "】 已强制关闭");
// String dealReason = delegateExecution.getVariableInstances().get("dealReason").getCachedValue().toString();
// String processInstanceId = delegateExecution.getProcessInstanceId();
//
// }
// 各个事件能得到的数据
// assignment:任务分配给指定的人员时触发。当流程到达userTask, assignment事件会在create事件之前发生。
// create:任务创建并设置所有属性后触发。
// complete:当任务完成,并尚未从运行数据中删除时触发。
// delete:只在任务删除之前发生。注意在通过completeTask正常完成时,也会执行。
}
需要注意的是,如果想要触发assignment事件,就必须将任务分配给指定的人员
核心代码分析:
类似前一个监听,此处实现了TaskListener接口,通过DelegateTask对象进行获取。
注意: 需要提前在模型中配置好对应监听
3.适配国产数据库:
3.1适配国产数据库实现原理:
由于activiti只进行支持一下的语法和语义,因此国产数据库不适配,需要进行源码调整
ProcessEngineConfigurationImpl
public static final String DATABASE_TYPE_H2 = "h2";
public static final String DATABASE_TYPE_HSQL = "hsql";
public static final String DATABASE_TYPE_MYSQL = "mysql";
public static final String DATABASE_TYPE_ORACLE = "oracle";
public static final String DATABASE_TYPE_POSTGRES = "postgres";
public static final String DATABASE_TYPE_MSSQL = "mssql";
public static final String DATABASE_TYPE_DB2 = "db2";
新增:
public static final String DATABASE_TYPE_OSCAR = "oscar";
修改对应语法语义判断逻辑,神通数据库适配oracle因此可以使用oracl语法语义,不需要进行重新编写
详情见连接:
https://blog.csdn.net/qq_42046342/article/details/105148196
笔者已经整理了一份对应的jar包,可以直接引用:
activiti适配神通数据库jar包
有兴趣的可以研究一下源码:
activiti5.22.0源码下载
nacos配置:
已脱敏
server:
servlet:
context-path: /act
spring:
redis:
host: ***
port: ***
password:
datasource:
driver-class-name: com.oscar.Driver
url: jdbc:oscar://***/***
username: ***
password: ***
Tomcat 配置:
server:
port: ****
logging:
level:
***.mapper: debug
Spring 配置:
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
enabled: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
application:
# 应用名称
name: ***-act
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8848
config:
# 配置中心地址
server-addr: 127.0.0.1:8848
# 配置文件格
file-extension: yml
# 共享配置
shared-dataids: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
activiti:
check-process-definitions: false
4.总结:
本项目使用的activiti版本是5.22.0
设计思路:通过记录全局的流程变量针对机构和角色动态进行判断下一节点审批人。
本次项目未使用activiti自带的用户表进行创建人员和分组,通过数据库创建的SYS_GROUP进行处理审批权限相关问题。
可以优化的地方:下一流程节点assignment(代理人)在记录时应该记录为机构和角色的条件,在审批流程中无论权限控制还是动态人员增删都通过此条件查询即可,现有逻辑是通过全局流程变量中记录的机构+角色进行处理。因此,会有增删人员时当前节点的审批对象已经固定,不能动态修改,只有在下一流程节点是新增的审批人才可以加入(需要流程流转一个节点)因此可做为优化项。
注:
在了解了工作流业务实现模式后可以进行activiti7的迁移,在表设计上和activiti5基本一致,因此不用太纠结与哪类版本。
附件:
工作流前后端交互流程简述版