独辟蹊径:我是如何用Java自创一套工作流引擎的(上)
CSDN 2024-07-05 09:05:03 阅读 56
作者:后端小肥肠
创作不易,未经允许严谨转载。
目录
1. 前言
2. 我为什么要自创一套工作流引擎
3. 表结构设计及关系讲解
3.1. 流程类别business_approval_workflow
3.1.1. 表结构
3.1.2. 表关系说明
3.2. 流程定义business_approval_workflow_detail
3.2.1. 表结构
3.2.2. 表关系说明
3.3. 流程任务记录approval_detail
3.3.1. 表结构
3.3.2. 表关系说明
3.4. 流程审批历史 approval_history
3.4.1. 表结构
3.4.2. 表关系说明
3.5. 申请表(流程实例)request
3.5.1. 表结构
4. 核心代码讲解
4.1. 设计用户表
4.2. 集成权限框架SpringSecurity
4.3. 工作流基础代码
4.4. 提交申请
4.5. 查看待我审批
4.6. 执行审批操作
4.7. 查看我已审批
5. 结语
1. 前言
在当前的技术生态中,工作流引擎如Activiti7和Flowable已经成为企业自动化的重要组成部分。然而,使用这些流行解决方案往往伴随着不小的学习成本,特别是Activiti7与Spring Security深度集成,使得不采用Security作为权限框架的系统面临集成上的挑战。此外,许多业务场景并不需要如此复杂的工作流框架。因此,出于对更灵活、更轻量级工作流解决方案的追求,我决定自主开发一套工作流引擎,以更好地满足特定的业务需求,同时减少对外部库的依赖。这不仅是一次技术上的挑战,也是对现有工作流理念的一次实践探索。
2. 我为什么要自创一套工作流引擎
熟悉我的博客读者都知道,我曾经基于Activiti7开发了一套成熟的工作流管理框架。然而,为何我还要自己动手创造一套新的工作流引擎呢?答案很简单:追求轻量化和更精准的边界。我长期专注于数据中台项目,涉及数据的汇集、管理与分发,其中审批流程即为其中一环。除了一个复杂的自定义流程设计和动态指定审批人的项目外,绝大多数系统仅需简单的审批流程,通常仅需1至2级审批,不涉及复杂的流程设计工具。
因此,我开发了一套轻量级工作流引擎,仅需要5张表就能完全满足简单审批流程的需求。这种定制化的设计不仅极大地简化了系统架构,还充分体现了对于业务需求精准匹配的追求。
这一决定不仅是技术上的挑战,更是对传统工作流理念的一次革新实践。
3. 表结构设计及关系讲解
3.1. 流程类别business_approval_workflow
3.1.1. 表结构
<code>CREATE TABLE "public"."business_approval_workflow" (
"id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"name" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
CONSTRAINT "business_approval_workflow_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."business_approval_workflow"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."business_approval_workflow"."name" IS '业务流程名称';
business_approval_workflow为流程类别表,定义系统中所需流程,id为业务流程id,name为业务流程名称。
3.1.2. 表关系说明
该表为流程类别表,系统中涉及的所有流程都记录在该表中。
3.2. 流程定义business_approval_workflow_detail
3.2.1. 表结构
<code>CREATE TABLE "public"."business_approval_workflow_detail" (
"id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"workflow_id" varchar(20) COLLATE "pg_catalog"."default" NOT NULL,
"serial_number" int4 NOT NULL,
"node_name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"node_username" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"is_final" varchar(1) COLLATE "pg_catalog"."default" NOT NULL,
CONSTRAINT "business_approval_workflow_detail_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."business_approval_workflow_detail"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."business_approval_workflow_detail"."workflow_id" IS '业务流程id';
COMMENT ON COLUMN "public"."business_approval_workflow_detail"."serial_number" IS '流程序号';
COMMENT ON COLUMN "public"."business_approval_workflow_detail"."node_name" IS '任务节点名称';
COMMENT ON COLUMN "public"."business_approval_workflow_detail"."node_username" IS '处理任务节点的username';
COMMENT ON COLUMN "public"."business_approval_workflow_detail"."is_final" IS '是否最后一道审批;,1表示是,0表示不是';
3.2.2. 表关系说明
business_approval_workflow_detail为流程定义表,定义流程表中的workflow_id对应business_approval_workflow中的id(逻辑外键)。
该表主要用于定义已知流程中有哪些任务节点(node_name),节点是顺序(serial_number),任务节点的审批人(node_username)。
3.3. 流程任务记录approval_detail
3.3.1. 表结构
approval_detail表记录流程审批过程中任务节点的状态。
<code>CREATE TABLE "public"."approval_detail" (
"id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"request_id" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"approver_username" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"approval_time" timestamp(6),
"next_approver_username" varchar(32) COLLATE "pg_catalog"."default",
"status" varchar(6) COLLATE "pg_catalog"."default" NOT NULL,
"remark" varchar(900) COLLATE "pg_catalog"."default",
"workflow_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"version" int4 NOT NULL DEFAULT 1,
"is_deleted" int4 NOT NULL DEFAULT 0,
"create_time" timestamp(6),
"update_time" timestamp(6),
"node_name" varchar(255) COLLATE "pg_catalog"."default",
"next_node_name" varchar(255) COLLATE "pg_catalog"."default",
CONSTRAINT "approval_detail_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."approval_detail"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."approval_detail"."id" IS '主键';
COMMENT ON COLUMN "public"."approval_detail"."request_id" IS 'request表id';
COMMENT ON COLUMN "public"."approval_detail"."approver_username" IS '审批人username';
COMMENT ON COLUMN "public"."approval_detail"."approval_time" IS '审批时间';
COMMENT ON COLUMN "public"."approval_detail"."next_approver_username" IS '下一个审批人的username';
COMMENT ON COLUMN "public"."approval_detail"."status" IS '审批状态;1.待我审批;2.通过;3.驳回; ';
COMMENT ON COLUMN "public"."approval_detail"."remark" IS '审批意见';
COMMENT ON COLUMN "public"."approval_detail"."workflow_id" IS '业务流程id';
COMMENT ON COLUMN "public"."approval_detail"."node_name" IS '当前任务节点名称';
COMMENT ON COLUMN "public"."approval_detail"."next_node_name" IS '下一任务节点名称';
3.3.2. 表关系说明
approval_detail表记录流程运行中的任务节点信息。request_id对应request表中的id(逻辑外键);workflow_id对应business_approval_workflow表中的id(逻辑外键),此表可以类比理解为Activiti7中的act_ru_task。
3.4. 流程审批历史 approval_history
3.4.1. 表结构
approval_history表记录流程运行时或运转后每个任务节点的历史信息。
<code>CREATE TABLE "public"."approval_history" (
"id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"request_id" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"approver_name" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"approval_time" timestamp(6),
"status" varchar(1) COLLATE "pg_catalog"."default",
"remark" varchar(900) COLLATE "pg_catalog"."default",
"workflow_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"version" int4 NOT NULL DEFAULT 1,
"is_deleted" int4 NOT NULL DEFAULT 0,
"applicant_phone" varchar(11) COLLATE "pg_catalog"."default",
"purpose" varchar(255) COLLATE "pg_catalog"."default",
"applicant_name" varchar(255) COLLATE "pg_catalog"."default",
"approver_username" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"create_time" timestamp(6),
"update_time" timestamp(6),
CONSTRAINT "approval_history_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."approval_history"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."approval_history"."request_id" IS '申请id';
COMMENT ON COLUMN "public"."approval_history"."approver_name" IS '审批人姓名';
COMMENT ON COLUMN "public"."approval_history"."approval_time" IS '审批时间';
COMMENT ON COLUMN "public"."approval_history"."status" IS '审批状态1.待我审批;2.通过;3.驳回';
COMMENT ON COLUMN "public"."approval_history"."remark" IS '审批意见';
COMMENT ON COLUMN "public"."approval_history"."workflow_id" IS '业务流程id';
COMMENT ON COLUMN "public"."approval_history"."applicant_phone" IS '申请人电话';
COMMENT ON COLUMN "public"."approval_history"."purpose" IS '申请理由';
COMMENT ON COLUMN "public"."approval_history"."applicant_name" IS '申请人姓名';
COMMENT ON COLUMN "public"."approval_history"."approver_username" IS '审批人username';
3.4.2. 表关系说明
在approval_history表中,request_id对应request表中的id(逻辑外键);workflow_id对应business_approval_workflow表中的id(逻辑外键)。
3.5. 申请表(流程实例)request
3.5.1. 表结构
request表为申请表,同时也记录着整个流程的审批状态,当用户提交申请时在该表中填充数据,当审批状态发生改变时需要同步更新request表中status的状态,与approval_detail中的status不同,request表中status记录的是整个流程的审批状态。
<code>CREATE TABLE "public"."request" (
"id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"workflow_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"create_time" timestamp(6) NOT NULL,
"purpose" varchar(900) COLLATE "pg_catalog"."default" NOT NULL,
"status" varchar(1) COLLATE "pg_catalog"."default" NOT NULL,
"version" int4 DEFAULT 1,
"applicant_name" varchar(255) COLLATE "pg_catalog"."default",
"applicant_phone" varchar(11) COLLATE "pg_catalog"."default",
"is_deleted" int4 DEFAULT 0,
"update_time" timestamp(6),
"applicat_username" varchar(50) COLLATE "pg_catalog"."default",
"applicat_unit" varchar(50) COLLATE "pg_catalog"."default",
"resource_id" varchar(255) COLLATE "pg_catalog"."default" DEFAULT ''::character varying
)
;
ALTER TABLE "public"."request"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."request"."workflow_id" IS '业务类型id';
COMMENT ON COLUMN "public"."request"."create_time" IS '申请时间;数据库自动填充';
COMMENT ON COLUMN "public"."request"."purpose" IS '使用目的';
COMMENT ON COLUMN "public"."request"."status" IS '审批状态;1.正在审核;2.通过;3.驳回';
COMMENT ON COLUMN "public"."request"."applicant_name" IS '申请人姓名';
COMMENT ON COLUMN "public"."request"."applicant_phone" IS '申请人电话';
COMMENT ON COLUMN "public"."request"."update_time" IS '更新时间';
COMMENT ON COLUMN "public"."request"."applicat_username" IS '申请用户名';
COMMENT ON COLUMN "public"."request"."applicat_unit" IS '申请单位';
COMMENT ON COLUMN "public"."request"."resource_id" IS '申请资源id';
4. 核心代码讲解
4.1. 设计用户表
CREATE TABLE "public"."sys_user" (
"id" varchar(40) COLLATE "pg_catalog"."default" NOT NULL,
"username" varchar(60) COLLATE "pg_catalog"."default",
"password" varchar(60) COLLATE "pg_catalog"."default",
"nick_name" varchar(60) COLLATE "pg_catalog"."default",
CONSTRAINT "sys_user_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."sys_user"
OWNER TO "postgres";
CREATE INDEX "username" ON "public"."sys_user" USING btree (
"username" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
);
COMMENT ON COLUMN "public"."sys_user"."id" IS '用户 ID';
COMMENT ON COLUMN "public"."sys_user"."username" IS '用户名';
COMMENT ON COLUMN "public"."sys_user"."password" IS '密码,加密存储';
COMMENT ON COLUMN "public"."sys_user"."nick_name" IS '昵称';
COMMENT ON TABLE "public"."sys_user" IS '用户信息表';
在表中写死几个用户,设定好他们的角色。
4.2. 集成权限框架SpringSecurity
工作流一般都需要跟权限框架联动,这里假设整合的是SpringSecurity(实际上我没整合权限框架,哈哈,我把用户都写死在代码里面了),整合步骤就略过了,我之出过SpringSecurity系列,包含了用户登录、权限和菜单分配,都有很详细的代码,链接如下:
【Spring Security系列】Spring Security+JWT+Redis实现用户认证登录及登出_spring security jwt 退出登录-CSDN博客
【Spring Security系列】基于Spring Security实现权限动态分配之用户-角色分配_springsecurity新增角色分配权限-CSDN博客
【Spring Security系列】基于Spring Security实现权限动态分配之菜单-角色分配及动态鉴权实践_springsecurity 角色关联菜单-CSDN博客
4.3. 工作流基础代码
本章将详细解说工作流基础代码,包含提交申请,查看待我审批,执行审批操作,查看我已审批接口。
4.4. 提交申请
controller层
<code> @GetMapping("")
public Boolean addRequest(@RequestBody RequestDTO requestDTO){
return requestService.addRequest(requestDTO);
}
RequestDTO编写
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestDTO {
private String workflowId;
private String purpose;
private String applicantName;
private String applicantPhone;
private String applicatUsername;
private String applicatUnit;
private String resourceId;
}
servic层
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 = workflowDetailService.findFirstNodeByWorkflowId(request.getWorkflowId());
//获取下一级节点 填充下级节点审批人
BusinessApprovalWorkflowDetail nextNode=workflowDetailService.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;
}
上述代码实现了一个方法 addRequest
,它接收一个 RequestDTO
对象作为参数,首先将其转换为 Request
对象并插入到数据库的 request
表中,设置流程状态为正在审核。然后根据流程定义查询首个审批节点和下一个审批节点的信息,并将这些信息作为审批详情记录插入到 approval_detail
表中,确保申请与审批流程的正确关联和处理。
4.5. 查看待我审批
controller层
@GetMapping("/pending-approval")
public List<ApprovalDetail> getPendingAppprovalList() {
return approvalDetailService.getPendingAppprovalList();
}
servic层
public List<ApprovalDetail> getPendingAppprovalList() {
//这里我写死了,实际获取应该走权限框架获取当前在线用户username
String username="xfc";code>
LambdaQueryWrapper<ApprovalDetail> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ApprovalDetail::getApproverUsername,username);
List<ApprovalDetail> approvalDetails = baseMapper.selectList(queryWrapper);
return approvalDetails;
}
4.6. 执行审批操作
controller层
@PostMapping("/approval")
public Boolean approvalApplication(@Validated @RequestBody ApprovalDTO approvalDTO) {
return approvalDetailService.approvalApplication(approvalDTO);
}
servic层
@Transactional
@Override
public Boolean approvalApplication(ApprovalDTO approvalDTO) {
// 这里我写死了,实际获取应该走权限框架获取当前在线用户 username
String username = "xfc";
// 审批人姓名,从用户表中获取
String name="小肥肠";code>
//查询出当前任务节点
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()); // 删除当前审批记录
// 填充 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); // 插入审批历史记录
// 更新 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.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():null);
approvalDetail.setApprovalTime(new Date());
approvalDetail.setStatus("1"); // 设置为待审批状态
baseMapper.updateById(approvalDetail);
}
} 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; // 或者根据实际需求返回其他业务逻辑
}
上述方法实现了审批申请的功能。首先,根据审批人的用户名查询当前需要审批的详细信息,并根据审批状态(通过或驳回)进行不同的处理逻辑。如果是审批通过并且当前节点为流程的最后一个节点,则删除当前审批记录并更新申请状态为通过;否则,更新到下一个审批节点的信息。如果是审批驳回,则直接删除当前审批记录,并更新申请状态为驳回。通过事务管理,保证了数据库操作的原子性和一致性。
4.7. 查看我已审批
controller层
@GetMapping("/approved")
public List<ApprovalHistory> getApprovedList() {
return approvalHistoryService.getApprovedList();
}
servic层
public List<ApprovalHistory> getApprovedList() {
//这里我写死了,实际获取应该走权限框架获取当前在线用户username
String username="xfc";code>
LambdaQueryWrapper<ApprovalHistory>queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ApprovalHistory::getApproverUsername,username);
List<ApprovalHistory> approvalHistories = baseMapper.selectList(queryWrapper);
return approvalHistories;
}
5. 结语
在本文中,我们系统地介绍了如何用Java语言自创一套工作流引擎。通过设计核心表结构和实现基础代码框架,我们打下了坚实的理论基础。在下篇文章中,我们将结合实际项目,展示工作流引擎的实际应用和效果。我们将深入探讨工作流引擎在复杂业务流程中的应用。如本文对您有帮助,请动动小手点点关注哦~
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。