引言紧接上文,了解如何配置与正确使用工单工作流以后,现在再来讲解下系统层面代码如何实现,在源码的基础上进行二次开发功能呢?整体功能参考官方文档:OA请假示例,自定义
第一章 需求背景
工单业务场景,领导发起工单,下游人员进行任务处理,主要包含接单、处理、评价环节,现借助工作流引擎辅助完成该功能。
第二章 工单流程表单定义
定义好工单业务表结构,记录了表单详情,每一层级审批人处理完毕后,会对当前表单中的记录进行更新。
CREATE TABLE ems.ems_form (
id int8 NOT NULL,
stationname varchar NULL,
workordertype varchar NULL,
workordersource varchar NULL,
devicetype varchar NULL,
devicename varchar NULL,
expecttime timestamp(6) NULL,
processrequirements varchar NULL,
processids varchar NULL,
status varchar NOT NULL,
takeordertime timestamp(6) NULL,
takeorderuserid int8 NULL,
takeorderusername varchar NULL,
processremark varchar NULL,
repaircosts float8 NULL,
logo varchar NULL,
assessremark varchar NULL,
ratelevel varchar NULL,
finishtime timestamp(6) NOT NULL DEFAULT now(),
iconprocess varchar NULL,
"result" int2 NOT NULL,
process_instance_id varchar(64) NULL,
creator varchar(64) NULL,
create_time timestamp(6) NOT NULL DEFAULT now(),
updater varchar(64) NULL,
update_time timestamp(6) NOT NULL DEFAULT now(),
deleted int2 NOT NULL DEFAULT 0,
tenant_id int8 NOT NULL DEFAULT 0,
workordername varchar NULL,
CONSTRAINT ems_form_pkey PRIMARY KEY (id)
);

第三章 工单处理流程
一、controller层定义好所有流程处理代码
@Tag(name = "管理后台 - 工单处理")
@RestController
@RequestMapping("/bpm/ems")
@Validated
public class EmsBpmController {
@Resource
private EmsBpmService emsBpmService;
@PostMapping("/create")
// @PreAuthorize("@ss.hasPermission('bpm:ems-leave:create')")
@Operation(summary = "创建请求申请")
public CommonResult<Long> createLeave(@Valid @RequestBody EmsBpmTaskReqVO createReqVO) {
return success(emsBpmService.createLeave(getLoginUserId(), createReqVO));
}
@PutMapping("/takeorder")
@Operation(summary = "接单按钮")
@Parameter(name = "id", description = "编号", required = true, example = "1024555651555")
// @PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> takeorder(String id) {
emsBpmService.takeorder(WebFrameworkUtils.getLoginUserId(), id);
return success(true);
}
@PutMapping("/takeorderandprocess")
@Operation(summary = "接单处理按钮")
// @PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> takeorderandprocess(@Valid @RequestBody EmsBpmTaskTakeOrderPorcessReqVO emsBpmTaskTakeOrderPorcessReqVO) {
emsBpmService.takeorderandprocess(WebFrameworkUtils.getLoginUserId(), emsBpmTaskTakeOrderPorcessReqVO);
return success(true);
}
@PutMapping("/access")
@Operation(summary = "评价按钮")
// @PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> access(@Valid @RequestBody EmsBpmTaskAccessReqVO emsBpmTaskAccessReqVO) {
emsBpmService.access(WebFrameworkUtils.getLoginUserId(), emsBpmTaskAccessReqVO);
return success(true);
}
@GetMapping("/page")
// @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')")
@Operation(summary = "获得工单分页")
public CommonResult<PageResult<EmsFormDO>> getEmsFormPage(@Valid EmsBpmTaskReqVO pageVO) {
PageResult<EmsFormDO> pageResult = emsBpmService.getEmsFormPage(getLoginUserId(), pageVO);
return success(pageResult);
}
}
二、对应的实现层serviceImpl
1、定义好流程标识符
@Service
@Validated
public class EmsBpmServiceImpl implements EmsBpmService {
/**
* 工单流程 对应的流程定义 KEY
*/
public static final String PROCESS_KEY = "ems_form";
2、逻辑实现,以接单流程举例
@Override
public void takeorder(Long userId, String id) {
// 1、拿到流程Task对象
Task task = taskService.createTaskQuery().taskId(id).singleResult();
// 2、校验流程实例存在
ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
}
// 完成任务,审批通过
taskService.complete(task.getId(), instance.getProcessVariables());
// ***上面都是源码引擎的工作流处理功能,下面加上咱们的业务功能 ***
// 接单的时候,默认添加当前接单人信息
String instanceId = instance.getProcessInstanceId();
EmsFormDO efd = emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, instanceId));
AdminUserDO aud = adminUserMapper.selectById(userId);
if (efd != null && efd.getTakeOrderUserId() == null) {
// 更新表结构为已接单状态,更新接单人信息
efd.setProcessIds("2");
efd.setIconprocess("2");
efd.setTakeOrderTime(LocalDateTime.now());
efd.setTakeOrderUserId(userId);
efd.setTakeOrderUserName(aud.getNickname());
emsBpmFormMapper.update(efd, new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, efd.getProcessInstanceId()));
}
//
// 更新任务拓展表为通过,源码bpm_task_ext表结构
taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()));
}
处理、评价流程相似,都是先对工作流引擎状态进行更新,再对业务表结构更新
3、展开阐述接单流程
第一步:根据Task对象中保存的表单实例id,查询流程实例是否处于运转中
// 1、拿到流程Task对象
Task task = taskService.createTaskQuery().taskId(id).singleResult();
// 2、校验流程实例存在
ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
源码:
点进getProcessInstance方法,BpmProcessInstanceServiceImpl类,调用flowable框架RuntimeService接口重写方法,返回流程实例对象
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
}
第二步:将任务处理为已结束流程,使得流程运转通过当前节点
// 完成任务,审批通过
taskService.complete(task.getId(), instance.getProcessVariables());
其中方法内部需要走多道认证,具体展开如下:
源码:
complete方法要想成功走完,需要通过多层认证。
芋道BpmParallelMultiInstanceBehavior类,自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
/**
* 重写该方法,主要实现两个功能:
* 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
* 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
*
* 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
*
* @param execution 执行任务
* @return 数量
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
Set<Long> assigneeUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(execution);
// 海尔工单定制化,为了让评价人可以流转回自己,在每一步处理人都加上发起人
// assigneeUserIds.add(WebFrameworkUtils.getLoginUserId());
execution.setVariable(super.collectionVariable, assigneeUserIds);
return assigneeUserIds.size();
}
在代码中第二步,获取任务的处理人时,有下述校验代码方式。
BPM 任务分配规则 Service 实现类,BpmTaskAssignRuleServiceImpl,找到任务处理规则表,定义的流程审批人,表中字段options
@Override
@DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
BpmTaskAssignRuleDO rule = getTaskRule(execution);
return calculateTaskCandidateUsers(execution, rule);
}

查看分配的流程规则,引用的是角色、用户、岗位等分配模式,返回可以审批的流程处理人
@VisibleForTesting
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
Set<Long> assigneeUserIds = null;
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByRole(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByPost(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUser(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByScript(execution, rule);
}
// 移除被禁用的用户
removeDisableUsers(assigneeUserIds);
// 如果候选人为空,抛出异常
if (CollUtil.isEmpty(assigneeUserIds)) {
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", execution.getId(),
execution.getProcessDefinitionId(), execution.getCurrentActivityId(), toJsonString(rule));
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
}
return assigneeUserIds;
}
小结: return assigneeUserIds.size(); 返回的内容有几个审批人数量
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
};
方法就会走几次,从而拿到每一个流程流转id的实例,并进行标记为已处理。
第三步:更新任务拓展表result字段,源码bpm_task_ext表结构, 根据枚举类中定义的属性更新标识符。芋道源码定义的拓展表,没有什么更多说的,不涉及flowable源码相关内容。
// 更新任务拓展表为通过
taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()));
4、展开阐述工单发起流程
涉及流程引擎内容:processInstanceApi.createProcessInstance方法,发起bpm工单流程,将用户id、工单标识符、表单id进行实例化,拿到processInstanceId。
@Override
public Long createLeave(Long userId, EmsBpmTaskReqVO createReqVO) {
// 插入 工单
EmsFormDO emsFormDO = new EmsFormDO();
BeanUtils.copyProperties(createReqVO, emsFormDO);
emsFormDO.setWorkordersource(createReqVO.getWorkOrderSource());
emsFormDO.setIconprocess("1");
emsFormDO.setStatus("1"); // 先随便填写一个数,后面优化字段去除
emsFormDO.setResult(BpmProcessInstanceResultEnum.START.getResult());
emsFormDO.setExpectTime(createReqVO.getExpectTime());
emsFormDO.setWorkOrderName(createReqVO.getWorkOrderName());
emsBpmFormMapper.insert(emsFormDO);
// 发起 BPM 流程
Map<String, Object> processInstanceVariables = new HashMap<>();
String processInstanceId = processInstanceApi.createProcessInstance(userId,
new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
.setBusinessKey(String.valueOf(emsFormDO.getId())));
// 将工作流的编号,更新到 工单表中
emsBpmFormMapper.updateById(new EmsFormDO().setId(emsFormDO.getId()).setProcessInstanceId(processInstanceId));
return emsFormDO.getId();
}
第四章 工单流转查询(待办、已办)
1、待办任务查询
@Override
public PageResult<EmsBpmTaskTodoRespVO> getEmsTodoTaskPage(Long userId, EmsBpmTaskTodoReqVO pageVO) {
// 查询待办任务
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByTaskCreateTime().desc(); // 创建时间倒序
// 执行查询
List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
if (CollUtil.isEmpty(tasks)) {
return PageResult.empty(taskQuery.count());
}
// 获得 ProcessInstance Map
Map<String, ProcessInstance> processInstanceMap =
processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
List<EmsFormDO> list = CollUtil.newArrayList();
// yongmin.bu新增,根据ProcessInstance-id拿到表单记录里,记录内容
for (ProcessInstance instance : processInstanceMap.values()) {
String id = instance.getId();
EmsFormDO efd = emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, id));
if (null != efd) {
// 定制化需求,判断处理流程是否是当前人
// 接单完再处理情况
if (efd.getTakeOrderUserId() != null && !efd.getTakeOrderUserId().equals("") && efd.getIconprocess().equals("2") && efd.getTakeOrderUserId().equals(userId)) {
list.add(emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, id)));
}
// 评价人处理情况
if (efd.getTakeOrderUserId() != null && !efd.getTakeOrderUserId().equals("") && efd.getIconprocess().equals("3")) {
list.add(emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, id)));
}
// 接单人处理情况
if (efd.getTakeOrderUserId() == null || efd.getTakeOrderUserId().equals("")) {
list.add(emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, id)));
}
}
}
// 获得 User Map
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
// 将查询参数进行封装,最后统一处理
Map<String, String> map = new HashMap<>();
map.put("stationName", pageVO.getStationName());
map.put("workOrderType", pageVO.getWorkOrderType());
map.put("workOrderSource", pageVO.getWorkOrderSource());
map.put("deviceType", pageVO.getDeviceType());
if (list.size() <= 0) {
return PageResult.empty(0L);
}
// 拼接结果,自动拆箱
return new PageResult<>(EmsBpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap, list, map),
Long.valueOf(list.size()));
}
第一步:根据登录用户的id查看分配给自己的待办任务
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByTaskCreateTime().desc(); // 创建时间倒序
第二步:分页查询每一个Task任务流程后,找到每一个流程对应的实例
第三步:遍历Map与自定义表单实例id进行匹配,通过这种方式来限制页面展示接单、处理、评价权限,通过业务模型来进行控制,即通过接单人是否为空与流程标识位来进行筛选。
第四步:根据传入条件进行条件过滤。统一返回有效的查询列表
2、已办任务查询
@Override
public PageResult<EmsBpmTaskDoneRespVO> getEmsDoneTaskPage(Long userId, EmsBpmTaskDoneReqVO pageVO) {
// 查询已办任务
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
.taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
// 执行查询
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
if (CollUtil.isEmpty(tasks)) {
return PageResult.empty(taskQuery.count());
}
// 获得 TaskExtDO Map
List<BpmTaskExtDO> bpmTaskExtDOs =
taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
// 获得 ProcessInstance Map
Map<String, HistoricProcessInstance> historicProcessInstanceMap =
processInstanceService.getHistoricProcessInstanceMap(
convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
// 获得 User Map
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
List<EmsFormDO> list = CollUtil.newArrayList();
// yongmin.bu新增,根据ProcessInstance-id拿到表单记录里,记录内容
for (HistoricProcessInstance instance : historicProcessInstanceMap.values()) {
String id = instance.getId();
EmsFormDO efd = emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, id));
if (null != efd) {
list.add(efd);
}
}
// 将查询参数进行封装,最后统一处理
Map<String, String> map = new HashMap<>();
map.put("workOrderSource", pageVO.getWorkOrderSource());
map.put("workOrderType", pageVO.getWorkOrderType());
map.put("deviceType", pageVO.getDeviceType());
map.put("stationName",pageVO.getStationName());
if (list.size() <= 0) {
return PageResult.empty(0L);
}
// 拼接结果
return new PageResult<>(
EmsBpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap, list, map),
taskQuery.count());
}
第一步:根据登录用户的id查看分配给自己的已办任务
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
.taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
// 执行查询
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
第二步:分页查询每一个Task任务流程后,找到每一个流程对应的实例
第三步:根据Task任务流程id,匹配拓展表中id,找到BpmTaskExtDO表中记录(ps:bpmTaskExtDOs、bpmTaskExtDOMap实际后续做统一列表返回时,个人认为可以优化步骤,仿照待办任务代码完成。但源码是这么设计,)
第四步:根据表单实例id与业务表中表单实例id进行匹配,找到对应的记录
// yongmin.bu新增,根据ProcessInstance-id拿到表单记录里,记录内容
for (HistoricProcessInstance instance : historicProcessInstanceMap.values()) {
String id = instance.getId();
EmsFormDO efd = emsBpmFormMapper.selectOne(new LambdaQueryWrapper<EmsFormDO>().eq(EmsFormDO::getProcessInstanceId, id));
if (null != efd) {
list.add(efd);
}
}
第五步:根据传入条件进行条件过滤。统一返回有效的查询列表
3、小结:上面代码中引用的,flowable有多个逻辑实现类,每一个都有自己的用法,下面来展开说说
1. RepositoryService
作用:
管理流程定义的部署、查询和删除。
提供对流程定义文件(BPMN、DMN、Form等)的存储和访问。
可以查询流程定义、部署资源、流程图表等。
常用方法:
java
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/my-process.bpmn20.xml")
.deploy();
2. RuntimeService
作用:
管理流程实例(Process Instance)和执行流(Execution)。
启动流程实例、触发流程操作(如信号事件)、查询运行中的流程。
处理流程变量(Variables)的读写。
常用方法:
java
ProcessInstance instance = runtimeService.startProcessInstanceByKey("myProcess");
3. TaskService
作用:
管理用户任务(User Task)的生命周期。
处理任务的创建、分配、完成、查询等操作。
支持任务变量(Task Variables)和候选人/组(Candidate Users/Groups)。
常用方法:
java
Task task = taskService.createTaskQuery().taskAssignee("user1").singleResult();
taskService.complete(task.getId());
4. HistoryService
作用:
提供历史数据查询,包括已完成的流程实例、任务、变量等。
支持生成流程执行的历史报告或统计分析。
与审计日志(Audit Log)相关。
常用方法:
java
HistoricProcessInstance historicInstance = historyService.createHistoricProcessInstanceQuery()
.finished()
.singleResult();
5. ManagementService
作用:
提供引擎管理和维护操作(如数据库表管理、作业查询)。
执行自定义SQL查询或操作引擎的底层任务(如异步作业管理)。
常用方法:
java
TableMetaData tableMetaData = managementService.getTableMetaData("ACT_RU_TASK");
总结
Flowable 的 Service 层是操作流程引擎的核心入口,每个 Service 职责明确:
运行时操作:RuntimeService、TaskService
历史数据:HistoryService
部署管理:RepositoryService
身份管理:IdentityService
底层管理:ManagementService
这些服务通过 ProcessEngine 接口获取:
java
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 其他 Service 类似
通过组合这些 Service,可以实现完整的业务流程管理(BPM)功能。
笔者:
在开发业务系统中,内嵌了工单处理功能;
运用了开源框架芋道,本身也自带工作流模块,并在此基础上做了一些修改;
以上是本人在工单模块的开发中,对主要功能做了个大致总结;
哦哦哦,还忘了工单撤回和工单详情页面的介绍,下次一定;
完结;散花~!




















暂无评论内容