
在企业级开发中,审批流、工单流转、状态机管控等流程类需求无处不在。传统重量级流程引擎(Activiti、Flowable、Camunda)虽功能完备,但存在部署复杂、学习成本高、对中小微型流程场景过度设计的痛点;而自研流程引擎又极易出现扩展性差、边界处理不到位、维护成本高的问题。
Easy Work作为一款开源轻量级Java流程引擎,完美解决了上述矛盾。它基于状态机模型设计,剥离了BPMN2.0规范中90%的低频复杂特性,仅保留核心的流程编排、任务管控、扩展监听能力,学习成本降低90%,性能较传统引擎提升3倍以上,是国内开发者中小微型流程场景的首选方案。
Easy Work采用分层架构设计,极致解耦,完美兼容Spring生态,整体架构如下:

各层核心职责:
流程引擎的本质,是对业务状态流转的标准化管控。Easy Work的底层核心是「流程定义+状态机+动作执行」三层模型,用通俗的话讲:
概念 | 核心定义 | 本质 | 易混淆点澄清 |
|---|---|---|---|
流程定义(ProcessDefinition) | 流程的静态模板,定义了流程的全链路规则 | 相当于Java中的类 | 流程定义修改后,已启动的流程实例不会生效,需升级版本号 |
流程实例(ProcessInstance) | 基于流程定义启动的具体流程,是流程的一次执行 | 相当于Java中的对象 | 一个流程定义可对应无数个流程实例,实例之间相互隔离 |
流程节点(ProcessNode) | 流程中的单个步骤,是流程的最小执行单元 | 相当于类中的方法 | 节点是静态定义,任务是节点运行时的动态实例 |
任务(TaskInfo) | 流程实例运行到用户任务节点时生成的待办事项 | 相当于方法的执行实例 | 只有USER_TASK类型节点会生成任务,其他节点自动执行 |
流转规则(Transition) | 节点之间的跳转规则,分为无条件流转和条件流转 | 相当于代码中的if-else/跳转逻辑 | 排他网关必须设置默认分支,否则会出现流程卡死 |
节点监听器(TaskListener) | 节点事件触发时执行的自定义逻辑,支持创建/完成/取消事件 | 相当于切面AOP | 监听器异常会阻断流程流转,非核心逻辑需做好异常处理 |
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath/>
</parent>
<groupId>com.jam</groupId>
<artifactId>easy-work-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>easy-work-demo</name>
<description>Easy Work流程引擎演示项目</description>
<properties>
<java.version>17</java.version>
<easy-work.version>2.2.0</easy-work.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.2.1-jre</guava.version>
<springdoc.version>2.6.0</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.william</groupId>
<artifactId>easy-work-spring-boot-starter</artifactId>
<version>${easy-work.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
spring:
datasource:
driver-class-name:com.mysql.cj.jdbc.Driver
url:jdbc:mysql://127.0.0.1:3306/easy_work_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username:root
password:root
transaction:
default-timeout:30
mybatis-plus:
mapper-locations:classpath*:/mapper/**/*.xml
type-aliases-package:com.jam.demo.entity
configuration:
map-underscore-to-camel-case:true
cache-enabled:false
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
easy-work:
auto-ddl:true
table-prefix:ew_
async-execute:false
springdoc:
api-docs:
enabled:true
path:/v3/api-docs
swagger-ui:
enabled:true
path:/swagger-ui.html
tags-sorter:alpha
operations-sorter:alpha
CREATE DATABASE IF NOT EXISTS easy_work_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLEIFNOTEXISTS`t_leave_apply` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`process_instance_id`varchar(64) COLLATE utf8mb4_unicode_ci DEFAULTNULLCOMMENT'流程实例ID',
`user_id`bigintNOTNULLCOMMENT'申请人ID',
`user_name`varchar(32) COLLATE utf8mb4_unicode_ci NOTNULLCOMMENT'申请人姓名',
`dept_id`bigintNOTNULLCOMMENT'部门ID',
`leave_type`tinyintNOTNULLCOMMENT'请假类型:1-事假,2-病假,3-年假',
`start_time` datetime NOTNULLCOMMENT'请假开始时间',
`end_time` datetime NOTNULLCOMMENT'请假结束时间',
`leave_days`decimal(10,1) NOTNULLCOMMENT'请假天数',
`leave_reason`varchar(512) COLLATE utf8mb4_unicode_ci NOTNULLCOMMENT'请假原因',
`status`tinyintNOTNULLDEFAULT'0'COMMENT'申请状态:0-草稿,1-审批中,2-已通过,3-已驳回',
`create_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`update_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
PRIMARY KEY (`id`),
KEY`idx_process_instance_id` (`process_instance_id`),
KEY`idx_user_id` (`user_id`),
KEY`idx_status` (`status`),
KEY`idx_create_time` (`create_time`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='请假申请表';
auto-ddl: true后,项目启动时会自动创建,无需手动执行。package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Easy Work演示项目启动类
* @author ken
*/
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
public class EasyWorkDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EasyWorkDemoApplication.class, args);
}
}
请假审批流程是企业最常用的流程场景,流程链路如下:

package com.jam.demo.enums;
import lombok.Getter;
/**
* 请假流程节点枚举
* @author ken
*/
@Getter
publicenum LeaveProcessNodeEnum {
START("start", "发起请假申请"),
MANAGER_APPROVE("manager_approve", "部门经理审批"),
HR_RECORD("hr_record", "人事备案"),
END("end", "流程结束");
privatefinal String nodeCode;
privatefinal String nodeName;
LeaveProcessNodeEnum(String nodeCode, String nodeName) {
this.nodeCode = nodeCode;
this.nodeName = nodeName;
}
}
package com.jam.demo.config;
import com.jam.demo.enums.LeaveProcessNodeEnum;
import com.william.easywork.definition.ProcessDefinition;
import com.william.easywork.definition.ProcessNode;
import com.william.easywork.enums.NodeTypeEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 请假流程定义配置
* @author ken
*/
@Configuration
publicclass LeaveProcessConfig {
/**
* 请假审批流程定义
* @return 流程定义对象
*/
@Bean
public ProcessDefinition leaveProcessDefinition() {
return ProcessDefinition.builder()
.processCode("leave_apply_process")
.processName("员工请假审批流程")
.version(1)
.description("员工请假申请,部门经理审批,人事备案的标准流程")
.startNodeCode(LeaveProcessNodeEnum.START.getNodeCode())
.endNodeCode(LeaveProcessNodeEnum.END.getNodeCode())
.addNode(buildStartNode())
.addNode(buildManagerApproveNode())
.addNode(buildHrRecordNode())
.addNode(buildEndNode())
.build();
}
/**
* 构建发起申请节点
* @return 流程节点对象
*/
private ProcessNode buildStartNode() {
return ProcessNode.builder()
.nodeCode(LeaveProcessNodeEnum.START.getNodeCode())
.nodeName(LeaveProcessNodeEnum.START.getNodeName())
.nodeType(NodeTypeEnum.START)
.addTransition(LeaveProcessNodeEnum.MANAGER_APPROVE.getNodeCode())
.build();
}
/**
* 构建部门经理审批节点
* @return 流程节点对象
*/
private ProcessNode buildManagerApproveNode() {
return ProcessNode.builder()
.nodeCode(LeaveProcessNodeEnum.MANAGER_APPROVE.getNodeCode())
.nodeName(LeaveProcessNodeEnum.MANAGER_APPROVE.getNodeName())
.nodeType(NodeTypeEnum.USER_TASK)
.addTransition("pass", LeaveProcessNodeEnum.HR_RECORD.getNodeCode())
.addTransition("reject", LeaveProcessNodeEnum.END.getNodeCode())
.build();
}
/**
* 构建人事备案节点
* @return 流程节点对象
*/
private ProcessNode buildHrRecordNode() {
return ProcessNode.builder()
.nodeCode(LeaveProcessNodeEnum.HR_RECORD.getNodeCode())
.nodeName(LeaveProcessNodeEnum.HR_RECORD.getNodeName())
.nodeType(NodeTypeEnum.SERVICE_TASK)
.addTransition(LeaveProcessNodeEnum.END.getNodeCode())
.build();
}
/**
* 构建结束节点
* @return 流程节点对象
*/
private ProcessNode buildEndNode() {
return ProcessNode.builder()
.nodeCode(LeaveProcessNodeEnum.END.getNodeCode())
.nodeName(LeaveProcessNodeEnum.END.getNodeName())
.nodeType(NodeTypeEnum.END)
.build();
}
}
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 请假申请实体类
* @author ken
*/
@Data
@TableName("t_leave_apply")
@Schema(description = "请假申请实体")
publicclass LeaveApply implements Serializable {
privatestaticfinallong serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
@Schema(description = "流程实例ID")
private String processInstanceId;
@Schema(description = "申请人ID")
private Long userId;
@Schema(description = "申请人姓名")
private String userName;
@Schema(description = "部门ID")
private Long deptId;
@Schema(description = "请假类型:1-事假,2-病假,3-年假")
private Integer leaveType;
@Schema(description = "请假开始时间")
private LocalDateTime startTime;
@Schema(description = "请假结束时间")
private LocalDateTime endTime;
@Schema(description = "请假天数")
private BigDecimal leaveDays;
@Schema(description = "请假原因")
private String leaveReason;
@Schema(description = "申请状态:0-草稿,1-审批中,2-已通过,3-已驳回")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.LeaveApply;
import org.apache.ibatis.annotations.Mapper;
/**
* 请假申请Mapper接口
* @author ken
*/
@Mapper
public interface LeaveApplyMapper extends BaseMapper<LeaveApply> {
}
package com.jam.demo.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 请假申请请求参数
* @author ken
*/
@Data
@Schema(description = "请假申请请求参数")
publicclass LeaveApplyReq {
@Schema(description = "申请人ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "申请人ID不能为空")
private Long userId;
@Schema(description = "申请人姓名", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "申请人姓名不能为空")
private String userName;
@Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "部门ID不能为空")
private Long deptId;
@Schema(description = "请假类型:1-事假,2-病假,3-年假", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "请假类型不能为空")
private Integer leaveType;
@Schema(description = "请假开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "请假开始时间不能为空")
private LocalDateTime startTime;
@Schema(description = "请假结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "请假结束时间不能为空")
private LocalDateTime endTime;
@Schema(description = "请假天数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "请假天数不能为空")
private BigDecimal leaveDays;
@Schema(description = "请假原因", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "请假原因不能为空")
private String leaveReason;
}
package com.jam.demo.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 请假审批请求参数
* @author ken
*/
@Data
@Schema(description = "请假审批请求参数")
publicclass LeaveApproveReq {
@Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "申请ID不能为空")
private Long applyId;
@Schema(description = "审批人ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "审批人ID不能为空")
private Long approveUserId;
@Schema(description = "审批人姓名", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "审批人姓名不能为空")
private String approveUserName;
@Schema(description = "审批结果:pass-通过,reject-驳回", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "审批结果不能为空")
private String approveResult;
@Schema(description = "审批意见")
private String approveRemark;
}
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.LeaveApply;
import com.jam.demo.req.LeaveApplyReq;
import com.jam.demo.req.LeaveApproveReq;
/**
* 请假申请服务接口
* @author ken
*/
publicinterface LeaveApplyService extends IService<LeaveApply> {
/**
* 发起请假申请
* @param req 请假申请请求参数
* @return 申请ID
*/
Long submitLeaveApply(LeaveApplyReq req);
/**
* 部门经理审批
* @param req 审批请求参数
*/
void managerApprove(LeaveApproveReq req);
/**
* 人事备案
* @param processInstanceId 流程实例ID
*/
void hrRecord(String processInstanceId);
}
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Maps;
import com.jam.demo.entity.LeaveApply;
import com.jam.demo.enums.LeaveProcessNodeEnum;
import com.jam.demo.mapper.LeaveApplyMapper;
import com.jam.demo.req.LeaveApplyReq;
import com.jam.demo.req.LeaveApproveReq;
import com.jam.demo.service.LeaveApplyService;
import com.william.easywork.core.ProcessEngine;
import com.william.easywork.entity.ProcessInstance;
import com.william.easywork.entity.TaskInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 请假申请服务实现类
* @author ken
*/
@Slf4j
@Service
publicclass LeaveApplyServiceImpl extends ServiceImpl<LeaveApplyMapper, LeaveApply> implements LeaveApplyService {
privatefinal ProcessEngine processEngine;
privatefinal PlatformTransactionManager transactionManager;
privatefinal LeaveApplyMapper leaveApplyMapper;
public LeaveApplyServiceImpl(ProcessEngine processEngine,
PlatformTransactionManager transactionManager,
LeaveApplyMapper leaveApplyMapper) {
this.processEngine = processEngine;
this.transactionManager = transactionManager;
this.leaveApplyMapper = leaveApplyMapper;
}
@Override
public Long submitLeaveApply(LeaveApplyReq req) {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(30);
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
if (ObjectUtils.isEmpty(req)) {
thrownew IllegalArgumentException("请假申请参数不能为空");
}
if (!StringUtils.hasText(req.getUserName())) {
thrownew IllegalArgumentException("申请人姓名不能为空");
}
if (req.getLeaveDays() == null || req.getLeaveDays().compareTo(BigDecimal.ZERO) <= 0) {
thrownew IllegalArgumentException("请假天数必须大于0");
}
LeaveApply leaveApply = new LeaveApply();
leaveApply.setUserId(req.getUserId());
leaveApply.setUserName(req.getUserName());
leaveApply.setDeptId(req.getDeptId());
leaveApply.setLeaveType(req.getLeaveType());
leaveApply.setStartTime(req.getStartTime());
leaveApply.setEndTime(req.getEndTime());
leaveApply.setLeaveDays(req.getLeaveDays());
leaveApply.setLeaveReason(req.getLeaveReason());
leaveApply.setStatus(1);
leaveApply.setCreateTime(LocalDateTime.now());
leaveApply.setUpdateTime(LocalDateTime.now());
leaveApplyMapper.insert(leaveApply);
Map<String, Object> variables = Maps.newHashMap();
variables.put("applyId", leaveApply.getId());
variables.put("userId", req.getUserId());
variables.put("userName", req.getUserName());
variables.put("deptId", req.getDeptId());
ProcessInstance processInstance = processEngine.getRuntimeService()
.startProcessInstanceByCode("leave_apply_process", variables);
leaveApply.setProcessInstanceId(processInstance.getInstanceId());
leaveApplyMapper.updateById(leaveApply);
transactionManager.commit(transactionStatus);
log.info("请假申请提交成功,申请ID:{},流程实例ID:{}", leaveApply.getId(), processInstance.getInstanceId());
return leaveApply.getId();
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("请假申请提交失败,参数:{}", req, e);
thrownew RuntimeException("请假申请提交失败:" + e.getMessage(), e);
}
}
@Override
public void managerApprove(LeaveApproveReq req) {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(30);
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
if (ObjectUtils.isEmpty(req)) {
thrownew IllegalArgumentException("审批参数不能为空");
}
if (!StringUtils.hasText(req.getApproveResult())) {
thrownew IllegalArgumentException("审批结果不能为空");
}
if (!"pass".equals(req.getApproveResult()) && !"reject".equals(req.getApproveResult())) {
thrownew IllegalArgumentException("审批结果只能是pass或reject");
}
LeaveApply leaveApply = leaveApplyMapper.selectById(req.getApplyId());
if (ObjectUtils.isEmpty(leaveApply)) {
thrownew IllegalArgumentException("请假申请不存在");
}
if (leaveApply.getStatus() != 1) {
thrownew IllegalArgumentException("申请状态异常,无法审批");
}
if (!StringUtils.hasText(leaveApply.getProcessInstanceId())) {
thrownew IllegalArgumentException("流程实例ID不存在");
}
TaskInfo taskInfo = processEngine.getTaskService()
.getCurrentTaskByInstanceId(leaveApply.getProcessInstanceId());
if (ObjectUtils.isEmpty(taskInfo)) {
thrownew IllegalArgumentException("当前没有待审批任务");
}
if (!LeaveProcessNodeEnum.MANAGER_APPROVE.getNodeCode().equals(taskInfo.getNodeCode())) {
thrownew IllegalArgumentException("当前节点不是部门经理审批节点");
}
Map<String, Object> variables = Maps.newHashMap();
variables.put("approveUserId", req.getApproveUserId());
variables.put("approveUserName", req.getApproveUserName());
variables.put("approveResult", req.getApproveResult());
variables.put("approveRemark", req.getApproveRemark());
processEngine.getTaskService()
.completeTask(taskInfo.getTaskId(), req.getApproveResult(), variables);
if ("pass".equals(req.getApproveResult())) {
leaveApply.setStatus(2);
} else {
leaveApply.setStatus(3);
}
leaveApply.setUpdateTime(LocalDateTime.now());
leaveApplyMapper.updateById(leaveApply);
transactionManager.commit(transactionStatus);
log.info("部门经理审批完成,申请ID:{},审批结果:{}", req.getApplyId(), req.getApproveResult());
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("部门经理审批失败,参数:{}", req, e);
thrownew RuntimeException("部门经理审批失败:" + e.getMessage(), e);
}
}
@Override
public void hrRecord(String processInstanceId) {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(30);
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
if (!StringUtils.hasText(processInstanceId)) {
thrownew IllegalArgumentException("流程实例ID不能为空");
}
LeaveApply leaveApply = lambdaQuery()
.eq(LeaveApply::getProcessInstanceId, processInstanceId)
.one();
if (ObjectUtils.isEmpty(leaveApply)) {
thrownew IllegalArgumentException("请假申请不存在");
}
TaskInfo taskInfo = processEngine.getTaskService()
.getCurrentTaskByInstanceId(processInstanceId);
if (ObjectUtils.isEmpty(taskInfo)) {
thrownew IllegalArgumentException("当前没有待处理任务");
}
if (!LeaveProcessNodeEnum.HR_RECORD.getNodeCode().equals(taskInfo.getNodeCode())) {
thrownew IllegalArgumentException("当前节点不是人事备案节点");
}
processEngine.getTaskService().completeTask(taskInfo.getTaskId());
leaveApply.setStatus(2);
leaveApply.setUpdateTime(LocalDateTime.now());
leaveApplyMapper.updateById(leaveApply);
transactionManager.commit(transactionStatus);
log.info("人事备案完成,流程实例ID:{}", processInstanceId);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("人事备案失败,流程实例ID:{}", processInstanceId, e);
thrownew RuntimeException("人事备案失败:" + e.getMessage(), e);
}
}
}
package com.jam.demo.listener;
import com.jam.demo.service.LeaveApplyService;
import com.william.easywork.annotation.NodeListener;
import com.william.easywork.enums.ListenerEventEnum;
import com.william.easywork.listener.NodeTaskListener;
import com.william.easywork.model.ListenerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* 人事备案节点任务监听器
* @author ken
*/
@Slf4j
@Component
@NodeListener(nodeCode = "hr_record", event = ListenerEventEnum.TASK_CREATE)
publicclass HrRecordTaskListener implements NodeTaskListener {
privatefinal LeaveApplyService leaveApplyService;
public HrRecordTaskListener(LeaveApplyService leaveApplyService) {
this.leaveApplyService = leaveApplyService;
}
@Override
public void onEvent(ListenerContext context) {
log.info("人事备案节点监听器触发,流程实例ID:{}", context.getInstanceId());
try {
String processInstanceId = context.getInstanceId();
if (!StringUtils.hasText(processInstanceId)) {
log.error("流程实例ID为空,无法执行人事备案");
return;
}
leaveApplyService.hrRecord(processInstanceId);
log.info("人事备案自动执行完成,流程实例ID:{}", processInstanceId);
} catch (Exception e) {
log.error("人事备案自动执行失败,流程实例ID:{}", context.getInstanceId(), e);
thrownew RuntimeException("人事备案执行失败:" + e.getMessage(), e);
}
}
}
package com.jam.demo.controller;
import com.jam.demo.req.LeaveApplyReq;
import com.jam.demo.req.LeaveApproveReq;
import com.jam.demo.service.LeaveApplyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* 请假申请Controller
* @author ken
*/
@RestController
@RequestMapping("/leave/apply")
@Tag(name = "请假申请管理", description = "请假申请流程相关接口")
publicclass LeaveApplyController {
privatefinal LeaveApplyService leaveApplyService;
public LeaveApplyController(LeaveApplyService leaveApplyService) {
this.leaveApplyService = leaveApplyService;
}
@PostMapping("/submit")
@Operation(summary = "发起请假申请", description = "提交请假申请并启动流程实例")
public ResponseEntity<Long> submitLeaveApply(@Valid @RequestBody LeaveApplyReq req) {
Long applyId = leaveApplyService.submitLeaveApply(req);
return ResponseEntity.ok(applyId);
}
@PostMapping("/manager/approve")
@Operation(summary = "部门经理审批", description = "部门经理对请假申请进行审批")
public ResponseEntity<Void> managerApprove(@Valid @RequestBody LeaveApproveReq req) {
leaveApplyService.managerApprove(req);
return ResponseEntity.ok().build();
}
@PostMapping("/hr/record")
@Operation(summary = "人事备案", description = "人事对审批通过的请假申请进行备案")
public ResponseEntity<Void> hrRecord(@RequestParam String processInstanceId) {
leaveApplyService.hrRecord(processInstanceId);
return ResponseEntity.ok().build();
}
}
process_code、instance_id、node_code、create_time字段建立联合索引,业务表的流程实例ID字段必须建立索引。Easy Work作为一款国产轻量级Java流程引擎,以极简的设计、极低的学习成本、极强的扩展性,完美解决了中小微型流程场景的痛点。它剥离了传统流程引擎的冗余特性,保留了核心的流程编排能力,让开发者可以在半小时内完成流程的设计、开发与上线。