(2)SpringBoot 3 + Vue 3 前后端分离项目,集成 Flowable
(1)认识 Flowable 流程引擎
(2)SpringBoot 3 + Vue 3 前后端分离项目,集成 Flowable(正在浏览)
1. pom.xml 添加 Maven 依赖
<!-- Flowable 流程引擎 --><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>7.1.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mysql 连接驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>${mysql-connector-j.version}</version></dependency><!-- spring-boot3 , mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- mybatis-plus 分页插件,确保版本和 MyBatis Plus 主包一致 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser</artifactId><version>${mybatis-plus-jsqlparser.version}</version></dependency><!-- mybatis-plus-join 多表查询 --><dependency><groupId>com.github.yulichang</groupId><artifactId>mybatis-plus-join-boot-starter</artifactId><version>${mybatis-plus-join.version}</version></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
2. application.yml 配置 Flowable,项目启动会自动在数据库创建 Flowable 需要的表
server:port: 8080spring:datasource:url: jdbc:mysql://localhost:3306/springboot3_vue3_satokenusername: ***password: ***driver-class-name: com.mysql.cj.jdbc.Driverflowable:# 是否开启异步执行器async-executor-activate: false# 建议初始化设置 true,生成所需要的表;之后设置为 false,则不会自动检查和更新数据库database-schema-update: false
3. 创建用户、组,并绑定用户-组的关系
3.1 根据 act_id_user、act_id_group、act_id_membership 表,分别创建 Entity、Controller、Service、ServiceImp、Mapper 。
3.2 Flowable 用户组 API
package com.dragon.springboot3vue3.controller.flowable;import cn.dev33.satoken.util.SaResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dragon.springboot3vue3.controller.dto.commonDto.StringsDTO;
import com.dragon.springboot3vue3.controller.flowable.dto.pageDto.ActIdGroupPageDto;
import com.dragon.springboot3vue3.entity.flowable.ActIdGroup;
import com.dragon.springboot3vue3.service.flowableService.ActIdGroupService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;@Tag(name = "Flowable 用户组 API")
@RestController
@RequestMapping("/actIdGroup")
public class ActIdGroupController {@Autowiredprivate ActIdGroupService groupService;@Operation(summary = "分页列表")@PostMapping("/list")public SaResult list(@RequestBody ActIdGroupPageDto pageDto){// 创建分页对象Page<ActIdGroup> page = new Page<>(pageDto.getCurrentPage(), pageDto.getPageSize());// 构造多表查询条件MPJLambdaWrapper<ActIdGroup> qw = new MPJLambdaWrapper<ActIdGroup>().like(StringUtils.isNotBlank(pageDto.getName()), ActIdGroup::getName, pageDto.getName());// 根据查询条件,将结果封装到分页对象Page<ActIdGroup> response = groupService.page(page, qw);return SaResult.ok().setData(response);}@Operation(summary = "新增或更新")@PostMapping("/saveOrUpdate")public SaResult saveOrUpdate(@RequestBody @Validated ActIdGroup actIdGroup){ActIdGroup group = new ActIdGroup();BeanUtils.copyProperties(actIdGroup, group);groupService.saveOrUpdate(group);return SaResult.ok();}@Operation(summary = "删除")@DeleteMapping("/remove")public SaResult remove(@RequestBody @Validated StringsDTO stringsDTO){groupService.removeByIds(stringsDTO.getStrings());return SaResult.ok();}@Operation(summary = "所有列表")@GetMapping("/getAll")public SaResult getAll(){return SaResult.ok().setData(groupService.list());}
}
3.3 Flowable 用户 API
package com.dragon.springboot3vue3.controller.flowable;import cn.dev33.satoken.util.SaResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dragon.springboot3vue3.controller.dto.commonDto.StringsDTO;
import com.dragon.springboot3vue3.controller.flowable.dto.entityDto.ActIdUserDto;
import com.dragon.springboot3vue3.controller.flowable.dto.pageDto.ActIdUserPageDto;
import com.dragon.springboot3vue3.entity.flowable.ActIdGroup;
import com.dragon.springboot3vue3.entity.flowable.ActIdMembership;
import com.dragon.springboot3vue3.entity.flowable.ActIdUser;
import com.dragon.springboot3vue3.service.flowableService.ActIdMembershipService;
import com.dragon.springboot3vue3.service.flowableService.ActIdUserService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;@Tag(name = "Flowable 用户 API")
@RestController
@RequestMapping("/actIdUser")
public class ActIdUserController {@Autowiredprivate ActIdUserService actIdUserService;@Autowiredprivate ActIdMembershipService actIdMembershipService;@Operation(summary = "分页列表")@PostMapping("/list")public SaResult list(@RequestBody ActIdUserPageDto pageDto){// 创建分页对象Page<ActIdUserDto> page = new Page<>(pageDto.getCurrentPage(), pageDto.getPageSize());// 构造多表查询条件MPJLambdaWrapper<ActIdUser> qw = new MPJLambdaWrapper<ActIdUser>().selectAs(ActIdUser::getId, ActIdUserDto::getId).selectAs(ActIdUser::getDisplayName, ActIdUserDto::getDisplayName).selectAs(ActIdGroup::getId, ActIdUserDto::getGroupId).selectAs(ActIdGroup::getName, ActIdUserDto::getGroupName).leftJoin(ActIdMembership.class, ActIdMembership::getUserId, ActIdUser::getId).leftJoin(ActIdGroup.class, ActIdGroup::getId, ActIdMembership::getGroupId).like(StringUtils.isNotBlank(pageDto.getDisplayName()), ActIdUser::getDisplayName, pageDto.getDisplayName()).like(StringUtils.isNotBlank(pageDto.getGroupName()), ActIdGroup::getName, pageDto.getGroupName());// 根据查询条件,将结果封装到分页对象Page<ActIdUserDto> response = actIdUserService.selectJoinListPage(page, ActIdUserDto.class,qw);return SaResult.ok().setData(response);}@Transactional@Operation(summary = "新增或更新")@PostMapping("/saveOrUpdate")public SaResult saveOrUpdate(@RequestBody @Validated ActIdUserDto userDto){// 1. 保存用户ActIdUser user = new ActIdUser();BeanUtils.copyProperties(userDto, user);actIdUserService.saveOrUpdate(user);// 2. 保存用户-组关联关系ActIdMembership membership = new ActIdMembership();membership.setUserId(userDto.getId());membership.setGroupId(userDto.getGroupId());actIdMembershipService.saveOrUpdate(membership);return SaResult.ok();}@Operation(summary = "删除")@DeleteMapping("/remove")public SaResult remove(@RequestBody @Validated StringsDTO stringsDTO){actIdUserService.removeByIds(stringsDTO.getStrings());return SaResult.ok();}@Operation(summary = "查询所有用户列表")@GetMapping("/getAll")public SaResult getAll(){return SaResult.ok().setData(actIdUserService.list());}
}
4. 流程定义 API(首先部署流程定义)
@Slf4j
@Tag(name = "Flowable 流程引擎 API")
@RestController
@RequestMapping("/process")
public class ProcessController {@Autowiredprivate TaskService taskService;@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate IdentityService identityService;@Autowiredprivate HistoryService historyService;@Autowiredprivate ActReProcDefService actReProcDefService;@Autowiredprivate ActIdMembershipService actIdMembershipService;@Operation(summary = "上传文件部署流程定义")@PostMapping("/uploadFileDeploy")public SaResult uploadFileDeploy(@RequestParam("file") MultipartFile file) {try {repositoryService.createDeployment().addBytes(file.getOriginalFilename(), file.getBytes()).name(file.getOriginalFilename()).deploy();return SaResult.ok("流程部署成功 ");} catch (Exception e) {return SaResult.error("流程部署失败 ");}}@Operation(summary = "本地文件部署流程定义")@PostMapping("/localFileDeploy")public SaResult localFileDeploy() {// 本地文件部署,resources -> process -> leave-request.bpmn20.xmlrepositoryService.createDeployment().addClasspathResource("process/leave-request.bpmn20.xml").name("请假流程").deploy();return SaResult.ok("部署成功");}@Operation(summary = "流程定义分页列表")@PostMapping("/list")public SaResult list(@RequestBody ActReProcDefPageDto pageDto) {// 创建分页对象Page<ActReProcDef> page = new Page<>(pageDto.getCurrentPage(), pageDto.getPageSize());// 构造多表查询条件MPJLambdaWrapper<ActReProcDef> qw = new MPJLambdaWrapper<ActReProcDef>().like(StringUtils.isNotBlank(pageDto.getName()), ActReProcDef::getName, pageDto.getName());// 根据查询条件,将结果封装到分页对象Page<ActReProcDef> response = actReProcDefService.page(page, qw);return SaResult.ok().setData(response);}@Operation(summary = "流程定义删除")@DeleteMapping("/remove")public SaResult remove(@RequestBody StringDTO stringDTO) {// 根据 deploymentId 删除,是否级联删除(流程启动了也可以删除)repositoryService.deleteDeployment(stringDTO.getStr(), true);return SaResult.ok();}@Operation(summary = "挂起流程定义")@PostMapping("/suspend")public SaResult suspend(@RequestBody StringDTO stringDTO) {repositoryService.suspendProcessDefinitionByKey(stringDTO.getStr());return SaResult.ok("挂起流程定义");}@Operation(summary = "激活流程定义")@PostMapping("/activate")public SaResult activate(@RequestBody StringDTO stringDTO) {repositoryService.activateProcessDefinitionByKey(stringDTO.getStr());return SaResult.ok("激活流程定义");}
}
5. 申请者开启流程实例(以请假流程为例)
/*** 一个流程实例通常会包含多个任务节点,这些任务会在流程执行过程中逐步生成和分配*/@Operation(summary = "开启请假流程实例")@PostMapping("/startLeave")public SaResult startLeave(@RequestBody HandleTaskDto handleTaskDto) {// 设置启动人identityService.setAuthenticatedUserId(handleTaskDto.getUserId());// 请假实例参数Map<String, Object> variables = new HashMap<>();variables.put("userId", handleTaskDto.getUserId());variables.put("groupId", handleTaskDto.getGroupId());// 启动流程实例,并设置流程变量runtimeService.startProcessInstanceByKey(handleTaskDto.getProcDefKey(), variables);return SaResult.ok();}
6. 申请者处理任务(填写请假信息)
@Operation(summary = "用户处理任务")@PostMapping("/assigneeHandle")public SaResult assigneeHandle(@RequestBody HandleTaskDto handleTaskDto) {Task task = taskService.createTaskQuery().taskAssignee(handleTaskDto.getUserId()).singleResult();// 请假实例参数Map<String, Object> variables = new HashMap<>();variables.put("userId", handleTaskDto.getUserId());variables.put("startTime", handleTaskDto.getStartTimeValue());variables.put("endTime", handleTaskDto.getEndTimeValue());variables.put("days", String.valueOf(handleTaskDto.getDays()));variables.put("reason", handleTaskDto.getReason());// 处理任务taskService.complete(task.getId(), variables);return SaResult.ok();}
7. 查看申请记录
@Operation(summary = "查询流程实例历史记录列表")@PostMapping("/getProcessInstanceHistory")public SaResult getProcessInstanceHistory(@RequestBody ProcessInstanceHistoryPageDto pageDto) {// 创建分页对象Page<ActHiProcinstDto> page = new Page<>(pageDto.getCurrentPage(),pageDto.getPageSize());// 创建查询对象HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc();// 根据启动人筛选流程实例if (StringUtils.isNotBlank(pageDto.getUserId())) {query = query.startedBy(pageDto.getUserId());}// 查询当前页的数据List<HistoricProcessInstance> historicTasks = query.listPage((int) ((pageDto.getCurrentPage() - 1) * pageDto.getPageSize()), Math.toIntExact(pageDto.getPageSize()));// 转换为 DTO 列表List<ActHiProcinstDto> list = historicTasks.stream().map(task -> {// 获取变量Map<String, Object> variables = getApplyVariables(task.getId());// 获取任务IDHistoricTaskInstance managerApprovalTask = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getId()).taskDefinitionKey("managerApproval").unfinished().singleResult();return ActHiProcinstDto.builder().id(task.getId()).name(task.getName()).startTime(DateUtil.toLocalDateTime(task.getStartTime())).endTime(DateUtil.toLocalDateTime(task.getEndTime())).procDefId(task.getProcessDefinitionId()).procDefName(task.getProcessDefinitionName()).procDefKey(task.getProcessDefinitionKey()).procInstId(task.getId()).startUserId(task.getStartUserId()).startTimeValue( (String) variables.get("startTime")).endTimeValue((String) variables.get("endTime")).days((String) variables.get("days")).reason((String) variables.get("reason")).userId((String) variables.get("userId")).result(variables.get("result") != null ? (Boolean) variables.get("result") : null).managerApprovalTaskId(managerApprovalTask!= null ? managerApprovalTask.getId() : null).build();}).toList();// 查询总记录数long total = query.count();// 封装分页结果page.setRecords(list);page.setTotal(total);return SaResult.ok().setData(page);}@Operation(summary = "查询历史记录中流程实例变量")private Map<String, Object> getApplyVariables(String processInstanceId) {HistoricVariableInstance days = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("days").singleResult();HistoricVariableInstance startTime = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("startTime").singleResult();HistoricVariableInstance endTime = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("endTime").singleResult();HistoricVariableInstance userId = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("userId").singleResult();HistoricVariableInstance reason = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("reason").singleResult();HistoricVariableInstance result = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("result").singleResult();HashMap<String, Object> map = new HashMap<>();map.put("days", days != null ? days.getValue() : null);map.put("startTime", startTime != null ? startTime.getValue() : null);map.put("endTime", endTime != null ? endTime.getValue() : null);map.put("userId", userId != null ? userId.getValue() : null);map.put("reason", reason != null ? reason.getValue() : null);map.put("result", result != null ? result.getValue() : null);return map;}
8. 查看流程进度
@Operation(summary = "查询历史活动详情列表")@GetMapping("/getHistoryTask")public SaResult getHistoryTask(@RequestParam String processInstanceId) {List<HistoricActivityInstance> historicTasks = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceEndTime().asc().list();List<ActRuTaskDto> list = historicTasks.stream().map(task -> ActRuTaskDto.builder().id(task.getActivityId()).name(task.getActivityName()).assignee(task.getAssignee()).createTime(DateUtil.toLocalDateTime(task.getStartTime())).endTime(DateUtil.toLocalDateTime(task.getEndTime())).procInstId(task.getProcessInstanceId()).procDefId(task.getProcessDefinitionId()).build()).toList();return SaResult.ok().setData(list);}
9. 审批者认领任务(认领候选组任务)
@Operation(summary = "认领候选组任务")@PostMapping("/claimGroupTask")public SaResult claimGroupTask(@RequestBody HandleTaskDto handleTaskDto) {// 判断用户是否属于该用户组ActIdMembership one = actIdMembershipService.lambdaQuery().eq(ActIdMembership::getUserId, handleTaskDto.getHandleUserId()).eq(ActIdMembership::getGroupId, handleTaskDto.getGroupId()).one();if(one == null){return SaResult.error("您无权限认领此任务");}// 认领任务taskService.claim(handleTaskDto.getManagerApprovalTaskId(), handleTaskDto.getHandleUserId());return SaResult.ok("任务已认领");}
10. 审批者处理任务(处理认领的任务)
@Operation(summary = "认领用户处理认领的任务")@PostMapping("/handleClaimTask")public SaResult handleClaimTask(@RequestBody HandleTaskDto handleTaskDto) {Task task = taskService.createTaskQuery().taskAssignee(handleTaskDto.getHandleUserId()).singleResult();if(task == null){return SaResult.error("任务已处理");}// 是否同意Map<String, Object> variables = new HashMap<>();variables.put("result", handleTaskDto.getResult());taskService.complete(task.getId(), variables);return SaResult.ok("任务处理完成");}
11. DTO
11.1 ActHiProcinstDto
package com.dragon.springboot3vue3.controller.flowable.dto.entityDto;import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;import java.time.LocalDateTime;@Builder
@Data
public class ActHiProcinstDto {@Schema(description = "主键ID")private String id;@Schema(description = "修订版本号(用于乐观锁控制)")private Integer rev;@Schema(description = "业务键")private String businessKey;@Schema(description = "开始时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@Schema(description = "结束时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;@Schema(description = "启动用户ID")private String startUserId;@Schema(description = "开始活动ID")private String startActId;@Schema(description = "结束活动ID")private String endActId;@Schema(description = "删除原因")private String deleteReason;@Schema(description = "租户ID")private String tenantId;@Schema(description = "流程实例名称")private String name;@Schema(description = "流程实例ID")private String procInstId;@Schema(description = "流程定义ID")private String procDefId;@Schema(description = "流程定义Key")private String procDefKey;@Schema(description = "流程定义名称")private String procDefName;@Schema(description = "用户ID")private String userId;@Schema(description = "开始时间")private String startTimeValue;@Schema(description = "结束时间")private String endTimeValue;@Schema(description = "请假天数")private String days;@Schema(description = "请假理由")private String reason;@Schema(description = "审批是否通过")private Boolean result;@Schema(description = "管理员审批的任务ID")private String managerApprovalTaskId;
}
11.2 ActIdUserDto
package com.dragon.springboot3vue3.controller.flowable.dto.entityDto;import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;@Data
public class ActIdUserDto {@Schema(description = "用户ID")private String id;@Schema(description = "用户显示名称")private String displayName;@Schema(description = "邮箱")private String email;@Schema(description = "密码")private String pwd;@Schema(description = "租户ID")private String tenantId;@NotEmpty@Schema(description = "用户组ID")private String groupId;@Schema(description = "用户组名称")private String groupName;
}
11.3 ActRuTaskDto
package com.dragon.springboot3vue3.controller.flowable.dto.entityDto;import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;import java.time.LocalDateTime;@Builder
@Data
public class ActRuTaskDto {@Schema(description = "主键")private String id;@Schema(description = "修订版本号(用于乐观锁控制)")private Integer rev;@Schema(description = "执行实例ID")private String executionId;@Schema(description = "流程实例ID")private String procInstId;@Schema(description = "流程定义ID")private String procDefId;@Schema(description = "任务定义ID")private String taskDefId;@Schema(description = "任务状态")private String state;@Schema(description = "任务名称")private String name;@Schema(description = "任务描述")private String description;@Schema(description = "任务定义键")private String taskDefKey;@Schema(description = "任务处理人")private String assignee;@Schema(description = "优先级")private Integer priority;@Schema(description = "创建时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;@Schema(description = "开始处理时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime inProgressTime;@Schema(description = "开始处理人")private String inProgressStartedBy;@Schema(description = "签收时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime claimTime;@Schema(description = "签收人")private String claimedBy;@Schema(description = "挂起时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime suspendedTime;@Schema(description = "挂起人")private String suspendedBy;@Schema(description = "处理中截止时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime inProgressDueDate;@Schema(description = "截止时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime dueDate;@Schema(description = "分类")private String category;@Schema(description = "挂起状态(1-活跃,2-挂起)")private Integer suspensionState;@Schema(description = "开始时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@Schema(description = "结束时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;@Schema(description = "请假天数")private String days;@Schema(description = "请假理由")private String reason;
}
11.4 HandleTaskDto
package com.dragon.springboot3vue3.controller.flowable.dto.entityDto;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;@Data
public class HandleTaskDto {@Schema(description = "开始时间")private String startTimeValue;@Schema(description = "结束时间")private String endTimeValue;@Schema(description = "请假天数")private float days;@Schema(description = "请假理由")private String reason;// 开启流程@Schema(description = "申请人ID")private String userId;@Schema(description = "流程定义Key")private String procDefKey;// 认领和处理任务@Schema(description = "审批者ID(认领任务者ID)")private String handleUserId;@Schema(description = "任务ID")private String managerApprovalTaskId;@Schema(description = "用户组ID")private String groupId;@Schema(description = "处理结果")private Boolean result;
}
11.5 PageDTO
package com.dragon.springboot3vue3.controller.dto.pageDto;import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;@Data
@Schema(description = "基础分页")
public class PageDTO {@NotNull@Schema(description = "当前页码")public Long currentPage;@NotNull@Schema(description = "每页记录数")public Long pageSize;
}
11.6 ActIdGroupPageDto
package com.dragon.springboot3vue3.controller.flowable.dto.pageDto;import com.dragon.springboot3vue3.controller.dto.pageDto.PageDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;@Data
public class ActIdGroupPageDto extends PageDTO {@Schema(description = "组名")private String name;
}
11.7 ActIdUserPageDto
package com.dragon.springboot3vue3.controller.flowable.dto.pageDto;import com.dragon.springboot3vue3.controller.dto.pageDto.PageDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;@Data
public class ActIdUserPageDto extends PageDTO {@Schema(description = "用户显示名称")private String displayName;@Schema(description = "用户组名")private String groupName;
}
11.8 ActReProcDefPageDto
package com.dragon.springboot3vue3.controller.flowable.dto.pageDto;import com.dragon.springboot3vue3.controller.dto.pageDto.PageDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;@Data
public class ActReProcDefPageDto extends PageDTO {@Schema(description = "流程定义名称")private String name;
}
11.9 ProcessInstanceHistoryPageDto
package com.dragon.springboot3vue3.controller.flowable.dto.pageDto;import com.dragon.springboot3vue3.controller.dto.pageDto.PageDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;@Data
public class ProcessInstanceHistoryPageDto extends PageDTO {@Schema(description = "申请人ID")private String userId;
}
12. 完整的流程处理代码(包含部署流程定义、开启流程实例、处理任务、查看历史任务等)
package com.dragon.springboot3vue3.controller.flowable;import cn.dev33.satoken.util.SaResult;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dragon.springboot3vue3.controller.dto.commonDto.StringDTO;
import com.dragon.springboot3vue3.controller.flowable.dto.entityDto.ActHiProcinstDto;
import com.dragon.springboot3vue3.controller.flowable.dto.entityDto.ActRuTaskDto;
import com.dragon.springboot3vue3.controller.flowable.dto.entityDto.HandleTaskDto;
import com.dragon.springboot3vue3.controller.flowable.dto.pageDto.ActReProcDefPageDto;
import com.dragon.springboot3vue3.controller.flowable.dto.pageDto.ProcessInstanceHistoryPageDto;
import com.dragon.springboot3vue3.entity.flowable.ActIdMembership;
import com.dragon.springboot3vue3.entity.flowable.ActReProcDef;
import com.dragon.springboot3vue3.entity.flowable.ActRuTask;
import com.dragon.springboot3vue3.service.flowableService.ActIdMembershipService;
import com.dragon.springboot3vue3.service.flowableService.ActReProcDefService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Slf4j
@Tag(name = "Flowable 流程引擎 API")
@RestController
@RequestMapping("/process")
public class ProcessController {@Autowiredprivate TaskService taskService;@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate IdentityService identityService;@Autowiredprivate HistoryService historyService;@Autowiredprivate ActReProcDefService actReProcDefService;@Autowiredprivate ActIdMembershipService actIdMembershipService;@Operation(summary = "上传文件部署流程定义")@PostMapping("/uploadFileDeploy")public SaResult uploadFileDeploy(@RequestParam("file") MultipartFile file) {try {repositoryService.createDeployment().addBytes(file.getOriginalFilename(), file.getBytes()).name(file.getOriginalFilename()).deploy();return SaResult.ok("流程部署成功 ");} catch (Exception e) {return SaResult.error("流程部署失败 ");}}@Operation(summary = "本地文件部署流程定义")@PostMapping("/localFileDeploy")public SaResult localFileDeploy() {// 本地文件部署,resources -> process -> leave-request.bpmn20.xmlrepositoryService.createDeployment().addClasspathResource("process/leave-request.bpmn20.xml").name("请假流程").deploy();return SaResult.ok("部署成功");}@Operation(summary = "流程定义分页列表")@PostMapping("/list")public SaResult list(@RequestBody ActReProcDefPageDto pageDto) {// 创建分页对象Page<ActReProcDef> page = new Page<>(pageDto.getCurrentPage(), pageDto.getPageSize());// 构造多表查询条件MPJLambdaWrapper<ActReProcDef> qw = new MPJLambdaWrapper<ActReProcDef>().like(StringUtils.isNotBlank(pageDto.getName()), ActReProcDef::getName, pageDto.getName());// 根据查询条件,将结果封装到分页对象Page<ActReProcDef> response = actReProcDefService.page(page, qw);return SaResult.ok().setData(response);}@Operation(summary = "流程定义删除")@DeleteMapping("/remove")public SaResult remove(@RequestBody StringDTO stringDTO) {// 根据 deploymentId 删除,是否级联删除(流程启动了也可以删除)repositoryService.deleteDeployment(stringDTO.getStr(), true);return SaResult.ok();}@Operation(summary = "挂起流程定义")@PostMapping("/suspend")public SaResult suspend(@RequestBody StringDTO stringDTO) {repositoryService.suspendProcessDefinitionByKey(stringDTO.getStr());return SaResult.ok("挂起流程定义");}@Operation(summary = "激活流程定义")@PostMapping("/activate")public SaResult activate(@RequestBody StringDTO stringDTO) {repositoryService.activateProcessDefinitionByKey(stringDTO.getStr());return SaResult.ok("激活流程定义");}/*** 一个流程实例通常会包含多个任务节点,这些任务会在流程执行过程中逐步生成和分配*/@Operation(summary = "开启请假流程实例")@PostMapping("/startLeave")public SaResult startLeave(@RequestBody HandleTaskDto handleTaskDto) {// 设置启动人identityService.setAuthenticatedUserId(handleTaskDto.getUserId());// 请假实例参数Map<String, Object> variables = new HashMap<>();variables.put("userId", handleTaskDto.getUserId());variables.put("groupId", handleTaskDto.getGroupId());// 启动流程实例,并设置流程变量runtimeService.startProcessInstanceByKey(handleTaskDto.getProcDefKey(), variables);return SaResult.ok();}@Operation(summary = "删除流程实例历史数据")@DeleteMapping("/deleteHistoricProcessInstance")public SaResult deleteHistoricProcessInstance(@RequestParam String processInstanceId) {try {// 查询流程实例是否存在(包括历史和运行中)HistoricProcessInstance historicInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();if (historicInstance == null) {return SaResult.error("流程实例不存在");}// 如果流程在运行中,先删除运行中实例if (historicInstance.getEndTime() == null) {runtimeService.deleteProcessInstance(processInstanceId, "管理员删除");}// 再删除历史流程实例historyService.deleteHistoricProcessInstance(processInstanceId);return SaResult.ok("流程实例删除成功");} catch (FlowableObjectNotFoundException e) {return SaResult.error("指定的流程实例不存在");} catch (Exception e) {return SaResult.error("删除历史流程实例失败: " + e.getMessage());}}@Operation(summary = "查询用户待办任务")@GetMapping("/getUserTasks")public SaResult getUserTasks(@RequestParam String userId) {List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).list();List<ActRuTask> list = tasks.stream().map(task -> ActRuTask.builder().id(task.getId()).name(task.getName()).assignee(task.getAssignee()).createTime(DateUtil.toLocalDateTime(task.getCreateTime())).procInstId(task.getProcessInstanceId()).procDefId(task.getProcessDefinitionId()).formKey(task.getFormKey()).build()).toList();return SaResult.ok().setData(list);}@Operation(summary = "用户处理任务")@PostMapping("/assigneeHandle")public SaResult assigneeHandle(@RequestBody HandleTaskDto handleTaskDto) {Task task = taskService.createTaskQuery().taskAssignee(handleTaskDto.getUserId()).singleResult();// 请假实例参数Map<String, Object> variables = new HashMap<>();variables.put("userId", handleTaskDto.getUserId());variables.put("startTime", handleTaskDto.getStartTimeValue());variables.put("endTime", handleTaskDto.getEndTimeValue());variables.put("days", String.valueOf(handleTaskDto.getDays()));variables.put("reason", handleTaskDto.getReason());// 处理任务taskService.complete(task.getId(), variables);return SaResult.ok();}@Operation(summary = "认领用户处理认领的任务")@PostMapping("/handleClaimTask")public SaResult handleClaimTask(@RequestBody HandleTaskDto handleTaskDto) {Task task = taskService.createTaskQuery().taskAssignee(handleTaskDto.getHandleUserId()).singleResult();if(task == null){return SaResult.error("任务已处理");}// 是否同意Map<String, Object> variables = new HashMap<>();variables.put("result", handleTaskDto.getResult());taskService.complete(task.getId(), variables);return SaResult.ok("任务处理完成");}@Operation(summary = "查询历史活动详情列表")@GetMapping("/getHistoryTask")public SaResult getHistoryTask(@RequestParam String processInstanceId) {List<HistoricActivityInstance> historicTasks = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceEndTime().asc().list();List<ActRuTaskDto> list = historicTasks.stream().map(task -> ActRuTaskDto.builder().id(task.getActivityId()).name(task.getActivityName()).assignee(task.getAssignee()).createTime(DateUtil.toLocalDateTime(task.getStartTime())).endTime(DateUtil.toLocalDateTime(task.getEndTime())).procInstId(task.getProcessInstanceId()).procDefId(task.getProcessDefinitionId()).build()).toList();return SaResult.ok().setData(list);}@Operation(summary = "查询流程实例历史记录列表")@PostMapping("/getProcessInstanceHistory")public SaResult getProcessInstanceHistory(@RequestBody ProcessInstanceHistoryPageDto pageDto) {// 创建分页对象Page<ActHiProcinstDto> page = new Page<>(pageDto.getCurrentPage(),pageDto.getPageSize());// 创建查询对象HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc();// 根据启动人筛选流程实例if (StringUtils.isNotBlank(pageDto.getUserId())) {query = query.startedBy(pageDto.getUserId());}// 查询当前页的数据List<HistoricProcessInstance> historicTasks = query.listPage((int) ((pageDto.getCurrentPage() - 1) * pageDto.getPageSize()), Math.toIntExact(pageDto.getPageSize()));// 转换为 DTO 列表List<ActHiProcinstDto> list = historicTasks.stream().map(task -> {// 获取变量Map<String, Object> variables = getApplyVariables(task.getId());// 获取任务IDHistoricTaskInstance managerApprovalTask = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getId()).taskDefinitionKey("managerApproval").unfinished().singleResult();return ActHiProcinstDto.builder().id(task.getId()).name(task.getName()).startTime(DateUtil.toLocalDateTime(task.getStartTime())).endTime(DateUtil.toLocalDateTime(task.getEndTime())).procDefId(task.getProcessDefinitionId()).procDefName(task.getProcessDefinitionName()).procDefKey(task.getProcessDefinitionKey()).procInstId(task.getId()).startUserId(task.getStartUserId()).startTimeValue( (String) variables.get("startTime")).endTimeValue((String) variables.get("endTime")).days((String) variables.get("days")).reason((String) variables.get("reason")).userId((String) variables.get("userId")).result(variables.get("result") != null ? (Boolean) variables.get("result") : null).managerApprovalTaskId(managerApprovalTask!= null ? managerApprovalTask.getId() : null).build();}).toList();// 查询总记录数long total = query.count();// 封装分页结果page.setRecords(list);page.setTotal(total);return SaResult.ok().setData(page);}@Operation(summary = "查询活跃任务列表(可认领和处理)")@GetMapping("/getActiveTask")public SaResult getActiveTask() {List<Task> tasks = taskService.createTaskQuery().active().list();List<ActRuTaskDto> list = tasks.stream().map(task -> {Map<String, Object> variables = taskService.getVariables(task.getId());// 将日期字符串转为LocalDateTimeDateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime startTime = LocalDateTime.parse((CharSequence) variables.get("startTime"), formatter);LocalDateTime endTime = LocalDateTime.parse((CharSequence) variables.get("endTime"), formatter);return ActRuTaskDto.builder().id(task.getId()).name(task.getName()).assignee(task.getAssignee()).createTime(DateUtil.toLocalDateTime(task.getCreateTime())).procInstId(task.getProcessInstanceId()).procDefId(task.getProcessDefinitionId()).startTime(startTime).endTime(endTime).days((String) variables.get("days")).reason((String) variables.get("reason")).build();}).toList();return SaResult.ok().setData(list);}@Operation(summary = "查询挂起的任务列表(任务暂停,不可处理)")@GetMapping("/getSuspendedTask")public SaResult getSuspendedTask() {List<Task> list = taskService.createTaskQuery().suspended().list();return SaResult.ok().setData(list);}@Operation(summary = "根据流程实例查询任务ID")@GetMapping("/getTaskId")public SaResult getTaskId(@RequestParam String processInstanceId) {String managerApprovalTaskId = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).taskDefinitionKey("managerApproval").unfinished().singleResult().getId();return SaResult.ok().setData(managerApprovalTaskId);}@Operation(summary = "查询历史记录中流程实例变量")@GetMapping("/getHistoryInstanceVariables")public SaResult getHistoryInstanceVariables(@RequestParam String processInstanceId) {return SaResult.ok().setData(getApplyVariables(processInstanceId));}@Operation(summary = "查询历史记录中流程实例变量")private Map<String, Object> getApplyVariables(String processInstanceId) {HistoricVariableInstance days = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("days").singleResult();HistoricVariableInstance startTime = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("startTime").singleResult();HistoricVariableInstance endTime = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("endTime").singleResult();HistoricVariableInstance userId = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("userId").singleResult();HistoricVariableInstance reason = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("reason").singleResult();HistoricVariableInstance result = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName("result").singleResult();HashMap<String, Object> map = new HashMap<>();map.put("days", days != null ? days.getValue() : null);map.put("startTime", startTime != null ? startTime.getValue() : null);map.put("endTime", endTime != null ? endTime.getValue() : null);map.put("userId", userId != null ? userId.getValue() : null);map.put("reason", reason != null ? reason.getValue() : null);map.put("result", result != null ? result.getValue() : null);return map;}@Operation(summary = "查询候选组任务")@GetMapping("/getGroupTasks")public SaResult getGroupTasks(@RequestParam String groupId) {List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(groupId).list();List<ActRuTaskDto> list = tasks.stream().map(task -> {Map<String, Object> variables = taskService.getVariables(task.getId());// 将日期字符串转为LocalDateTimeDateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime startTime = LocalDateTime.parse((CharSequence) variables.get("startTime"), formatter);LocalDateTime endTime = LocalDateTime.parse((CharSequence) variables.get("endTime"), formatter);return ActRuTaskDto.builder().id(task.getId()).name(task.getName()).assignee(task.getAssignee()).createTime(DateUtil.toLocalDateTime(task.getCreateTime())).procInstId(task.getProcessInstanceId()).procDefId(task.getProcessDefinitionId()).startTime(startTime).endTime(endTime).days((String) variables.get("days")).reason((String) variables.get("reason")).build();}).toList();return SaResult.ok().setData(list);}@Operation(summary = "认领候选组任务")@PostMapping("/claimGroupTask")public SaResult claimGroupTask(@RequestBody HandleTaskDto handleTaskDto) {// 判断用户是否属于该用户组ActIdMembership one = actIdMembershipService.lambdaQuery().eq(ActIdMembership::getUserId, handleTaskDto.getHandleUserId()).eq(ActIdMembership::getGroupId, handleTaskDto.getGroupId()).one();if(one == null){return SaResult.error("您无权限认领此任务");}// 认领任务taskService.claim(handleTaskDto.getManagerApprovalTaskId(), handleTaskDto.getHandleUserId());return SaResult.ok("任务已认领");}
}