作者:后端小肥肠
创作不易,未经允许严禁转载。
1. 前言
在上一篇博客中,我们详细介绍了如何利用Java语言从零开始打造一套工作流引擎的基础架构。通过设计核心表结构和实现基础代码框架,我们建立了一个坚实的理论基础。今天,我们迈入《独辟蹊径:我是如何用Java自创一套工作流引擎的(下)》,将深入探讨这一引擎在实际项目中的应用和效果。
2. 项目场景模拟
本章我们将以实际项目场景来模拟自研工作流引擎的使用,分别是申请数据资源的流程和请假申请流程。
2.1. 申请数据资源流程
2.1.1. 技术流程
假设申请数据的审批级数为2级。申请数据资源的流程图如下:
在上述流程图中,从普通用户,一级审批人员,二级审批人员视角呈现了申请数据资源的整体流程;
-
用户提交审批数据表单,填入申请人信息(姓名、电话),申请理由和需要申请的数资源;
-
一级审批人员收到用户提交的申请后进行审批,如果驳回则整个流程结束,如果通过则进入下一审批环节;
-
一级审批通过后二级审批人员可进行审批,如果驳回则整个流程结束,如何通过则开放数据下载链接,用户可根据链接下载申请的数据。
2.2. 请假申请流程
2.2.1. 技术流程
请假流程如下:
在上述流程中,从用户和一级审批人员的角度呈现了整个请假流程:
-
用户提交审批数据表单,填入申请人信息(姓名、电话)、申请理由、请假天数;
-
一级审批人员收到用户提交的申请后进行审批,如果驳回则整个流程结束,同时通知用户流程未通过,如果通过则结束流程。
2.3. 技术实现
要在工作流中集成以上两套流程,需要基于一下几个步骤实现:
- 设计流程定义,在business_approval_workflow新建数据审批流程和请假流程。
- 设计流程细节,设计数据审批流程和请假流程的节点细节。
上图中,申请业务数据包含两个流程节点,第一个节点审批人为admin,第二个节点审批人为super;请假流程包含一个流程节点,审批人为admin。
- 编写提交申请接口。
提交申请业务数据流程接口直接使用《独辟蹊径:我是如何用Java自创一套工作流引擎的(上)》中提交申请接口就行:
java
public Boolean addRequest(RequestDTO requestDTO) {
Request request= BeanCopyUtils.copyBean(requestDTO,Request.class);
request.setStatus("1");//设置整个流程状态为正在审核
// 1. 插入数据到 request 表
baseMapper.insert(request);
// 2. 根据 workflow_id 查询业务流程的节点信息,找到 serial_number 为 1 的节点,即流程开始时的第一个节点
BusinessApprovalWorkflowDetail firstNode = workflowDetaiSlService.findFirstNodeByWorkflowId(request.getWorkflowId());
//获取下一级节点 填充下级节点审批人
BusinessApprovalWorkflowDetail nextNode=workflowDetaiSlService.getNextNodeByPreNode(firstNode);
if (firstNode != null) {
// 创建一个 approval_detail 记录示例,需要根据具体情况设置字段值
ApprovalDetail approvalDetail = new ApprovalDetail();
approvalDetail.setRequestId(request.getId()); // 假设设置关联的 request_id
approvalDetail.setApproverUsername(firstNode.getNodeUsername()); // 设置首次节点的审批人用户名
approvalDetail.setApprovalTime(new Date());
approvalDetail.setNextApproverUsername(nextNode.getNodeUsername());//设置下游节点的审批人用户名
approvalDetail.setStatus("1"); // 设置初始状态为待审批
approvalDetail.setWorkflowId(request.getWorkflowId());
approvalDetail.setNodeName(firstNode.getNodeName());
approvalDetail.setNextNodeName(nextNode.getNodeName());
// 插入数据到 approval_detail 表
approvalDetailService.save(approvalDetail);
} else {
// 如果未找到对应的节点,根据实际需求进行错误处理或日志记录
throw new RuntimeException("Unable to find the first node for workflow id: " + request.getWorkflowId());
}
return true;
}
提交请假流程接口,在编写提交请假流程接口前,需要先明确请假申请表的表结构:
sql
CREATE TABLE "public"."leave_request" (
"id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"workflow_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"purpose" varchar(900) COLLATE "pg_catalog"."default" NOT NULL,
"leave_days" int2 NOT NULL,
"applicant_name" varchar(50) COLLATE "pg_catalog"."default",
"applicat_username" varchar(50) COLLATE "pg_catalog"."default",
"applicant_phone" varchar(11) COLLATE "pg_catalog"."default",
"version" int4 DEFAULT 1,
"is_deleted" int4 DEFAULT 0,
"create_time" timestamp(6) NOT NULL,
"update_time" timestamp(6)
)
;
ALTER TABLE "public"."leave_request"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."leave_request"."workflow_id" IS '业务流程id';
COMMENT ON COLUMN "public"."leave_request"."purpose" IS '请假理由';
COMMENT ON COLUMN "public"."leave_request"."leave_days" IS '请假天数';
COMMENT ON COLUMN "public"."leave_request"."applicant_name" IS '申请人姓名';
COMMENT ON COLUMN "public"."leave_request"."applicat_username" IS '申请人用户名';
COMMENT ON COLUMN "public"."leave_request"."applicant_phone" IS '申请人电话';
编写controller层:
java
@GetMapping("")
public Boolean addRequest(@Validated @RequestBody LeaveRequestDTO leaveRequestDTO){
return leaveRequestService.addRequest(leaveRequestDTO);
}
编写LeaveRequestDTO:
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeaveRequestDTO {
private String workflowId;
private String purpose;
private Integer leaveDays;
private String applicantName;
private String applicantPhone;
private String applicatUsername;
}
编写service层:
java
public Boolean addRequest(LeaveRequestDTO leaveRequestDTO) {
LeaveRequest leaveRequest= BeanCopyUtils.copyBean(leaveRequestDTO,LeaveRequest.class);
// 1. 插入数据到 request 表
baseMapper.insert(leaveRequest);
// 2. 根据 workflow_id 查询业务流程的节点信息,找到 serial_number 为 1 的节点,即流程开始时的第一个节点
BusinessApprovalWorkflowDetail firstNode = workflowDetaiSlService.findFirstNodeByWorkflowId(leaveRequest.getWorkflowId());
//获取下一级节点 填充下级节点审批人
BusinessApprovalWorkflowDetail nextNode=workflowDetaiSlService.getNextNodeByPreNode(firstNode);
if (firstNode != null) {
// 创建一个 approval_detail 记录示例,需要根据具体情况设置字段值
ApprovalDetail approvalDetail = new ApprovalDetail();
approvalDetail.setRequestId(leaveRequest.getId()); // 假设设置关联的 request_id
approvalDetail.setApproverUsername(firstNode.getNodeUsername()); // 设置首次节点的审批人用户名
approvalDetail.setApprovalTime(new Date());
approvalDetail.setNextApproverUsername(nextNode.getNodeUsername());//设置下游节点的审批人用户名
approvalDetail.setStatus("1"); // 设置初始状态为待审批
approvalDetail.setWorkflowId(leaveRequest.getWorkflowId());
approvalDetail.setNodeName(firstNode.getNodeName());
approvalDetail.setNextNodeName(nextNode.getNodeName());
// 插入数据到 approval_detail 表
approvalDetailService.save(approvalDetail);
} else {
// 如果未找到对应的节点,根据实际需求进行错误处理或日志记录
throw new RuntimeException("Unable to find the first node for workflow id: " + leaveRequest.getWorkflowId());
}
return true;
}
上述代码实现了提交请假申请的功能:首先将从DTO转换后的请假请求数据插入数据库,然后根据流程ID查询流程的第一个节点信息,设置首次节点的审批人并插入到审批详情表中,状态设置为待审批。
- 基于策略模式优化审批接口。
审批申请方法在上篇中如下:
java
@Transactional
@Override
public Boolean approvalApplication(ApprovalDTO approvalDTO) {
// 这里我写死了,实际获取应该走权限框架获取当前在线用户 username
String username = "xfc";
// 审批人姓名,从用户表中获取
String name="小肥肠";
//查询出当前任务节点
ApprovalDetail approvalDetail = baseMapper.selectById(approvalDTO.getId());
//获取当前审批的申请信息
Request request = requestMapper.selectById(approvalDetail.getRequestId());
if(request==null){
throw new RuntimeException("申请id有误");
}
// 审批通过
if (approvalDTO.getStatus().equals("2")) {
// 根据 workflow_id 和 node_name 联查 business_approval_workflow_detail 表,获取当前流程是否为最后节点即 is_final=1
BusinessApprovalWorkflowDetail currentWorkflowDetail = businessApprovalWorkflowDetailService.findByWorkflowIdAndNodeName(approvalDTO.getWorkflowId(), approvalDetail.getNodeName());
if (currentWorkflowDetail != null && currentWorkflowDetail.getIsFinal().equals("1")) {
// 如果是最后节点,则删除该条数据,填充 approval_history 表,根据 request 表修改 request 数据的 status 为 2
baseMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录
// 更新 request 表中的状态为 2(通过)
request.setStatus("2");
requestMapper.updateById(request);
} else {
// 如果不是最后节点,则更新 business_approval_workflow_detail 为下一个节点审批信息
BusinessApprovalWorkflowDetail nextNode = businessApprovalWorkflowDetailService.getNextNodeByPreNode(currentWorkflowDetail);
// 获取下一级节点的更下一级
BusinessApprovalWorkflowDetail nextNextNode= businessApprovalWorkflowDetailService.getNextNodeByPreNode(nextNode);
// 更新当前 approval_detail 表中的审批人和下一个审批人信息
approvalDetail.setApproverUsername(nextNode.getNodeUsername());
approvalDetail.setNodeName(nextNextNode.getNodeName());
approvalDetail.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():"");
approvalDetail.setNextNodeName(nextNextNode!=null?nextNextNode.getNodeName():"");
approvalDetail.setApprovalTime(new Date());
approvalDetail.setStatus("1"); // 设置为待审批状态
baseMapper.updateById(approvalDetail);
}
// 填充 approval_history 表
ApprovalHistory approvalHistory = new ApprovalHistory();
approvalHistory.setRequestId(request.getId());
approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
approvalHistory.setApprovalTime(new Date());
approvalHistory.setStatus("2"); // 通过
approvalHistory.setRemark(approvalDTO.getRemark());
approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
approvalHistory.setApplicantPhone(request.getApplicantPhone());
approvalHistory.setPurpose(request.getPurpose());
approvalHistory.setApplicantName(request.getApplicantName());
approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录
} else if (approvalDTO.getStatus().equals("3")) {
// 审批驳回
baseMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录
// 填充 approval_history 表
ApprovalHistory approvalHistory = new ApprovalHistory();
approvalHistory.setRequestId(request.getId());
approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
approvalHistory.setApprovalTime(new Date());
approvalHistory.setStatus("3"); // 驳回
approvalHistory.setRemark(approvalDTO.getRemark());
approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
approvalHistory.setApplicantPhone(request.getApplicantPhone());
approvalHistory.setPurpose(request.getPurpose());
approvalHistory.setApplicantName(request.getApplicantName());
approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录
// 更新 request 表中的状态为 3(驳回)
request.setStatus("3");
requestMapper.updateById(request);
}
return true; // 或者根据实际需求返回其他业务逻辑
}
上述代码其实是针对申请数据资源流程的审批操作,那如果要申请别的流程该怎么做呢?常规操作应该是在上述代码中增加if-else判断操作,根据不通业务进行士审批操作,但是随着业务流程增加,就会新增许多if-else操作,代码会十分雍总,代码可读性较差,可以通过引入策略工厂来解决上述问题。步骤如下:
1.新增审批策略工厂
java
@Service
public class ApprovalFactory {
@Autowired
ApprovalDataRequestService approvalDataRequestService;
@Autowired
ApprovalLeaveRequestService approvalLeaveRequestService;
private static Map<String, Function<ApprovalDTO,Boolean>> approvalMap = null;
@PostConstruct
public void init(){
approvalMap=new HashMap<>();
approvalMap.put("2",approvalDTO->approvalDataRequestService.approvalApplication(approvalDTO));
approvalMap.put("1",approvalDTO ->approvalLeaveRequestService.approvalApplication(approvalDTO));
}
public Boolean approvalApplication(ApprovalDTO approvalDTO) {
return approvalMap.get(approvalDTO.getWorkflowId()).apply(approvalDTO);
}
}
上述代码为针对审批操作的策略工厂,在类初始化过程中(使用 @PostConstruct
注解的 init()
方法),通过静态的 approvalMap
对象将审批动作和对应的处理函数关联起来。具体来说:
- 当
workflowId
为 "2" 时,映射到approvalDataRequestService
的approvalApplication
方法处理数据请求的审批逻辑。 - 当
workflowId
为 "1" 时,映射到approvalLeaveRequestService
的approvalApplication
方法处理请假请求的审批逻辑。
最后,approvalApplication(ApprovalDTO approvalDTO)
方法根据传入的 approvalDTO
中的 workflowId
从 approvalMap
中获取相应的处理函数,并执行该函数来完成审批操作,返回处理结果,controller层直接调用策略工厂即可:
java
@PostMapping("/approval")
public Boolean approvalApplication(@Validated @RequestBody ApprovalDTO approvalDTO) {
return approvalFactory.approvalApplication(approvalDTO);
}
- 新增数据资源审批类
java
@Service
@Slf4j
public class ApprovalDataRequestService {
@Autowired
IBusinessApprovalWorkflowDetailService businessApprovalWorkflowDetailService;
@Autowired
RequestMapper requestMapper;
@Autowired
ApprovalHistoryMapper approvalHistoryMapper;
@Autowired
ApprovalDetailMapper approvalDetailMapper;
@Transactional
public Boolean approvalApplication(ApprovalDTO approvalDTO) {
// 这里我写死了,实际获取应该走权限框架获取当前在线用户 username
String username = "xfc";
// 审批人姓名,从用户表中获取
String name="小肥肠";
//查询出当前任务节点
ApprovalDetail approvalDetail = approvalDetailMapper.selectById(approvalDTO.getId());
//获取当前审批的申请信息
Request request = requestMapper.selectById(approvalDetail.getRequestId());
if(request==null){
throw new RuntimeException("申请id有误");
}
// 审批通过
if (approvalDTO.getStatus().equals("2")) {
// 根据 workflow_id 和 node_name 联查 business_approval_workflow_detail 表,获取当前流程是否为最后节点即 is_final=1
BusinessApprovalWorkflowDetail currentWorkflowDetail = businessApprovalWorkflowDetailService.findByWorkflowIdAndNodeName(approvalDTO.getWorkflowId(), approvalDetail.getNodeName());
if (currentWorkflowDetail != null && currentWorkflowDetail.getIsFinal().equals("1")) {
// 如果是最后节点,则删除该条数据,填充 approval_history 表,根据 request 表修改 request 数据的 status 为 2
approvalDetailMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录
// 更新 request 表中的状态为 2(通过)
request.setStatus("2");
requestMapper.updateById(request);
} else {
// 如果不是最后节点,则更新 business_approval_workflow_detail 为下一个节点审批信息
BusinessApprovalWorkflowDetail nextNode = businessApprovalWorkflowDetailService.getNextNodeByPreNode(currentWorkflowDetail);
// 获取下一级节点的更下一级
BusinessApprovalWorkflowDetail nextNextNode= businessApprovalWorkflowDetailService.getNextNodeByPreNode(nextNode);
// 更新当前 approval_detail 表中的审批人和下一个审批人信息
approvalDetail.setApproverUsername(nextNode.getNodeUsername());
approvalDetail.setNodeName(nextNextNode.getNodeName());
approvalDetail.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():"");
approvalDetail.setNextNodeName(nextNextNode!=null?nextNextNode.getNodeName():"");
approvalDetail.setApprovalTime(new Date());
approvalDetail.setStatus("1"); // 设置为待审批状态
approvalDetailMapper.updateById(approvalDetail);
}
// 填充 approval_history 表
ApprovalHistory approvalHistory = new ApprovalHistory();
approvalHistory.setRequestId(request.getId());
approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
approvalHistory.setApprovalTime(new Date());
approvalHistory.setStatus("2"); // 通过
approvalHistory.setRemark(approvalDTO.getRemark());
approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
approvalHistory.setApplicantPhone(request.getApplicantPhone());
approvalHistory.setPurpose(request.getPurpose());
approvalHistory.setApplicantName(request.getApplicantName());
approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录
} else if (approvalDTO.getStatus().equals("3")) {
// 审批驳回
approvalDetailMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录
// 填充 approval_history 表
ApprovalHistory approvalHistory = new ApprovalHistory();
approvalHistory.setRequestId(request.getId());
approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取
approvalHistory.setApprovalTime(new Date());
approvalHistory.setStatus("3"); // 驳回
approvalHistory.setRemark(approvalDTO.getRemark());
approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());
approvalHistory.setApplicantPhone(request.getApplicantPhone());
approvalHistory.setPurpose(request.getPurpose());
approvalHistory.setApplicantName(request.getApplicantName());
approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取
approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录
// 更新 request 表中的状态为 3(驳回)
request.setStatus("3");
requestMapper.updateById(request);
}
return true; // 或者根据实际需求返回其他业务逻辑
}
}
3.新增请假审批类
java
@Service
public class ApprovalLeaveRequestService {
public Boolean approvalApplication(ApprovalDTO approvalDTO) {
/**
* 一样的逻辑,把对request表的操作改为leave_request
*/
return true;
}
}
3. 结语
在本文中,针对《独辟蹊径:我是如何用Java自创一套工作流引擎的(上)》中的工作流基础代码进行了结合实际项目的扩展,本工作流引擎适用于任何相对简单的审批场景,有新的业务流程仅需针对申请表单和审批逻辑进行接口新增和策略工厂扩展即可,如本文对你有帮助请一键三连哦~