文章目录
一、概述
Warm-Flow 作为 Dromara 社区出品的国产轻量级工作流引擎,凭借其简洁的 API 设计、丰富的协作模式和多 ORM 框架适配能力,已逐步成为众多企业级项目的首选工作流解决方案。然而,从"能跑起来"到"生产级稳定运行",中间还隔着架构设计、性能优化、安全加固、监控运维等一系列关键环节。
本文将聚焦 Warm-Flow 在生产环境中的最佳实践与高级特性,从项目分层架构设计入手,涵盖部署运维、性能调优、多租户、软删除、流程设计器定制、流程图集成、框架整合、常见问题排查以及企业级实战案例等 14 个核心主题,帮助开发者构建健壮、可维护的工作流系统。
二、架构设计最佳实践
2.1 推荐的项目分层架构
清晰的分层架构是项目可维护性的基石。以下是推荐的 Warm-Flow 项目目录结构:
your-project/
├── src/main/java/com/example/
│ ├── controller/ # 控制层
│ │ ├── FlowDesignerController.java # 设计器页面
│ │ ├── FlowInstanceController.java # 流程实例操作
│ │ └── FlowTaskController.java # 待办/已办查询
│ ├── service/ # 业务服务层(推荐封装层)
│ │ ├── FlowIntegrationService.java # 统一工作流入口
│ │ ├── LeaveFlowService.java # 请假流程业务
│ │ └── ExpenseFlowService.java # 报销流程业务
│ ├── handler/ # Warm-Flow 扩展点
│ │ ├── CustomPermissionHandler.java # 自定义权限处理器
│ │ ├── CustomConditionExpression.java # 自定义条件表达式
│ │ └── GlobalFlowListener.java # 全局监听器
│ ├── listener/ # 节点级别监听器
│ │ ├── FinanceApprovalListener.java
│ │ └── HrReviewListener.java
│ ├── config/ # 配置类
│ │ ├── WarmFlowConfig.java # 自定义配置
│ │ └── FlowSecurityConfig.java # 安全配置
│ ├── converter/ # 转换器
│ │ ├── FlowInstanceConverter.java # 实体→VO 转换
│ │ └── FlowTaskConverter.java
│ ├── vo/ # 视图对象
│ │ ├── FlowInstanceVO.java
│ │ ├── TodoTaskVO.java
│ │ └── ApprovalTrailVO.java
│ └── dto/ # 数据传输对象
│ ├── StartFlowCommand.java
│ ├── ApproveCommand.java
│ └── RejectCommand.java
└── src/main/resources/
├── warm-flow/ # 工作流资源
│ ├── flow-definitions/ # 流程定义 JSON 文件
│ └── templates/ # 表单模板
└── application.yml # 应用配置
2.2 核心设计原则
原则一:业务与流程分离
不要在 Controller 中直接操作 FlowService,而应通过 Service 层统一封装业务流程。以下是正确做法的示例:
/**
* ✅ 正确做法:通过 Service 层统一封装
* 优势:职责清晰、可测试、可复用
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class LeaveFlowService {
@Autowired
private FlowService flowService;
@Autowired
private LeaveOrderService leaveOrderService;
@Autowired
private NotificationService notificationService;
/**
* 发起请假流程(事务性操作)
*/
public FlowResult startLeave(StartLeaveCommand cmd) {
// 1. 业务校验
LeaveOrder order = leaveOrderService.getById(cmd.getOrderId());
if (order == null) {
return FlowResult.fail("请假单不存在");
}
if (!"DRAFT".equals(order.getStatus())) {
return FlowResult.fail("只有草稿状态的请假单才能提交");
}
// 2. 更新业务表状态
leaveOrderService.updateStatus(cmd.getOrderId(), "PENDING_APPROVAL");
// 3. 构建流程变量
Map<String, Object> variable = new HashMap<>();
variable.put("orderId", cmd.getOrderId());
variable.put("leaveDays", order.getDays());
variable.put("leaveType", order.getType());
variable.put("applicantId", order.getApplicantId());
variable.put("deptId", order.getDeptId());
// 4. 发起流程
FlowParams params = FlowParams.build()
.flowCode("leave")
.handler(cmd.getOperatorId())
.variable(variable)
.message("发起" + order.getType() + "申请:" + order.getDays() + "天")
.ext("{\"orderId\":" + cmd.getOrderId() + "}");
FlowInstance instance = flowService.start(params);
// 5. 关联业务表和流程实例
leaveOrderService.linkInstanceId(cmd.getOrderId(), instance.getId());
// 6. 发送通知
notificationService.notifyNextApprover(instance.getId());
return FlowResult.success(instance.getId(),
"请假申请已提交", instance.getFlowStatus());
}
}
原则二:命令模式封装请求
推荐使用 Command 对象封装所有流程操作请求,统一入参格式,便于校验和日志记录:
// ========== 基础命令对象 ==========
@Data
public abstract class BaseFlowCommand implements Serializable {
@NotBlank(message = "操作人不能为空")
private String operatorId;
private Long instanceId;
private String comment;
private String nextHandler;
private Map<String, Object> ext;
}
// ========== 发起流程命令 ==========
@Data
@EqualsAndHashCode(callSuper = true)
public class StartFlowCommand extends BaseFlowCommand {
@NotBlank(message = "流程编码不能为空")
private String flowCode;
@NotBlank(message = "业务ID不能为空")
private String businessId;
private Map<String, Object> variables;
private boolean urgent;
}
// ========== 审批命令 ==========
@Data
@EqualsAndHashCode(callSuper = true)
public class ApproveCommand extends BaseFlowCommand {
private boolean approved;
private String targetNodeCode; // 退回目标节点编码
}
// ========== 协作命令 ==========
@Data
@EqualsAndHashCode(callSuper = true)
public class CollaborateCommand extends BaseFlowCommand {
@NotNull(message = "协作类型不能为空")
private Integer cooperateType; // 2转办 3委派 6加签 7减签
@NotEmpty(message = "目标用户不能为空")
private List<String> targetUsers;
}
原则三:统一的异常处理体系
构建工作流异常层次结构,便于精确捕获和处理不同类型的异常:
// 工作流基础异常
public class FlowException extends RuntimeException {
private final String code;
private final String instanceId;
public FlowException(String code, String message, String instanceId) {
super(message);
this.code = code;
this.instanceId = instanceId;
}
}
// 权限不足
public class FlowNoPermissionException extends FlowException {
public FlowNoPermissionException(String nodeCode, String handler) {
super("NO_PERMISSION",
"用户[" + handler + "]无权限操作节点[" + nodeCode + "]", null);
}
}
// 流程状态异常
public class FlowInvalidStatusException extends FlowException {
public FlowInvalidStatusException(String currentStatus, String operation) {
super("INVALID_STATUS",
"当前状态[" + currentStatus + "]不允许执行[" + operation + "]操作", null);
}
}
// 全局异常处理
@RestControllerAdvice
public class GlobalFlowExceptionHandler {
@ExceptionHandler(FlowNoPermissionException.class)
public ResponseEntity<Result<Void>> handleNoPermission(FlowException e) {
log.warn("工作流权限异常: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(Result.error(e.getCode(), e.getMessage()));
}
@ExceptionHandler(FlowInvalidStatusException.class)
public ResponseEntity<Result<Void>> handleInvalidStatus(FlowException e) {
log.warn("工作流状态异常: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(Result.error(e.getCode(), e.getMessage()));
}
@ExceptionHandler(FlowException.class)
public ResponseEntity<Result<Void>> handleFlowException(FlowException e) {
log.error("工作流异常: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Result.error(e.getCode(), e.getMessage()));
}
}
2.3 数据库设计建议
推荐在业务表中增加流程关联字段,实现业务表与流程表的双向关联:
-- 请假单表示例
CREATE TABLE `leave_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`applicant_id` varchar(40) NOT NULL COMMENT '申请人ID',
`leave_type` varchar(20) NOT NULL COMMENT '请假类型',
`start_date` date NOT NULL COMMENT '开始日期',
`end_date` date NOT NULL COMMENT '结束日期',
`days` int NOT NULL COMMENT '天数',
`reason` varchar(500) DEFAULT NULL COMMENT '原因',
-- ⭐ 流程关联字段(推荐)
`flow_instance_id` bigint DEFAULT NULL COMMENT '流程实例ID',
`flow_status` varchar(20) DEFAULT NULL COMMENT '流程状态(冗余,方便查询)',
`current_node_name` varchar(100) DEFAULT NULL COMMENT '当前节点名称(冗余)',
`status` varchar(20) NOT NULL DEFAULT 'DRAFT'
COMMENT '业务状态:DRAFT/PENDING/APPROVED/REJECTED/CANCELLED',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_flow_instance_id` (`flow_instance_id`),
KEY `idx_applicant_status` (`applicant_id`, `status`)
) ENGINE=InnoDB COMMENT='请假单表';
-- 可选:创建联合视图方便查询
CREATE OR REPLACE VIEW `v_leave_with_flow` AS
SELECT
lo.*,
fi.flow_code,
fi.flow_status as flow_instance_status,
fi.node_code as flow_current_node,
fi.node_name as flow_current_node_name,
fi.variable as flow_variables,
fd.flow_name
FROM leave_order lo
LEFT JOIN flow_instance fi ON lo.flow_instance_id = fi.id
LEFT JOIN flow_definition fd ON fi.definition_id = fd.id;
三、生产环境部署指南
3.1 Maven 依赖配置
<dependencies>
<!-- 核心依赖:根据 ORM 框架选择 -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb-starter</artifactId>
<version>1.8.8</version>
</dependency>
<!-- 设计器插件 -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-modes-sb</artifactId>
<version>1.8.8</version>
</dependency>
<!-- 流程图 UI 插件 -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-vue3-ui</artifactId>


993

被折叠的 条评论
为什么被折叠?



