实例与任务
实例与任务
业务用途
流程实例(Instance)是流程模型的一次运行,审批任务(Task)是流程实例中需要某个人处理的审批节点。本章覆盖完整的审批生命周期:发起流程 -> 待办处理 -> 审批/驳回 -> 流程完成/驳回。
核心引擎 SimpleFlowEngine 提供两个关键方法:
startProcess(model, variables):创建实例(running),解析simpleModel,发抄送通知,创建首个审批任务completeTask(task, action, opinion):处理任务(approve/reject),会签节点特殊处理,最后一个任务完成则实例completed
涉及文件
后端
| 文件 | 说明 |
|---|---|
backend/src/main/java/com/lowcode/service/bpm/SimpleFlowEngine.java | 核心引擎(startProcess / completeTask / checkAndCompleteInstance) |
backend/src/main/java/com/lowcode/controller/bpm/BpmInstanceController.java | 实例控制器(/api/bpm/instance) |
backend/src/main/java/com/lowcode/controller/bpm/BpmTaskController.java | 任务控制器(/api/bpm/task) |
backend/src/main/java/com/lowcode/service/impl/bpm/BpmInstanceServiceImpl.java | 实例服务 |
backend/src/main/java/com/lowcode/service/impl/bpm/BpmTaskServiceImpl.java | 任务服务 |
backend/src/main/java/com/lowcode/entity/bpm/BpmInstance.java | 实例实体 |
backend/src/main/java/com/lowcode/entity/bpm/BpmTask.java | 任务实体 |
前端
| 文件 | 说明 |
|---|---|
frontend/src/views/bpm/todo.vue | 待办/已办任务页 |
frontend/src/views/bpm/instance.vue | 流程实例管理页 |
数据库表
bpm_instance(流程实例表)
CREATE TABLE bpm_instance (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT DEFAULT 1,
def_id BIGINT, -- 流程模型 ID
def_key VARCHAR(100), -- 流程标识
def_name VARCHAR(200), -- 流程名称
business_key VARCHAR(200), -- ★ 业务键(formCode:dataId)
business_type VARCHAR(50), -- ★ 业务类型(formCode)
title VARCHAR(500), -- 流程标题
start_user_id BIGINT, -- 发起人 ID
start_user_name VARCHAR(50), -- 发起人姓名
start_dept_id BIGINT, -- 发起部门 ID
start_time TIMESTAMP, -- 发起时间
end_time TIMESTAMP, -- 结束时间
duration BIGINT, -- 耗时(毫秒)
status VARCHAR(20) DEFAULT 'running', -- ★ running/completed/rejected/recalled/revoked
current_node VARCHAR(200), -- 当前节点
priority INT DEFAULT 0, -- 优先级
form_data CLOB, -- 表单数据 JSON
-- ...
);bpm_task(审批任务表)
CREATE TABLE bpm_task (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT DEFAULT 1,
instance_id BIGINT, -- 流程实例 ID
def_id BIGINT, -- 流程模型 ID
node_id VARCHAR(100), -- 节点标识
node_name VARCHAR(200), -- 节点名称
task_name VARCHAR(200), -- 任务名称
assignee_id BIGINT, -- 处理人 ID
assignee_name VARCHAR(50), -- 处理人姓名
candidate_user_ids VARCHAR(500), -- 候选用户 ID
candidate_role_ids VARCHAR(500), -- 候选角色 ID
status VARCHAR(20) DEFAULT 'pending', -- ★ pending/completed/rejected/transferred/delegated/revoked/sign_passed/sign_rejected
priority INT DEFAULT 0,
due_time TIMESTAMP, -- 到期时间
claim_time TIMESTAMP, -- 签收时间
complete_time TIMESTAMP, -- 完成时间
opinion VARCHAR(1000), -- 审批意见
form_data CLOB, -- 表单数据
is_sign INT DEFAULT 0, -- ★ 是否会签节点
sign_type VARCHAR(20), -- 会签类型: AND/OR
sign_rule VARCHAR(20), -- 会签规则: ALL/HALF/RATIO
pass_ratio INT, -- 通过比例
assignee_config CLOB, -- 审批人配置 JSON
approved_count INT DEFAULT 0, -- 已通过人数
rejected_count INT DEFAULT 0, -- 已拒绝人数
total_sign_count INT DEFAULT 0, -- 总审批人数
-- ...
);后端实现
SimpleFlowEngine.startProcess()
@Transactional
public BpmInstance startProcess(BpmProcessDefinitionInfo model, Map<String, Object> variables) {
// 1. 创建流程实例
BpmInstance instance = new BpmInstance();
instance.setDefId(model.getId());
instance.setDefKey(model.getKey());
instance.setDefName(model.getName());
instance.setBusinessKey(variables.get("businessKey").toString()); // formCode:dataId
instance.setBusinessType(variables.get("businessType").toString()); // formCode
instance.setTitle(variables.get("title").toString());
instance.setStartUserId(Long.parseLong(variables.get("startUserId").toString()));
instance.setStartUserName(variables.get("startUserName").toString());
instance.setStartTime(LocalDateTime.now());
instance.setStatus("running");
instance.setFormData(variables.get("formData").toString());
instanceMapper.insert(instance);
// 2. 解析 simpleModel 获取流程节点
List<Map<String, Object>> nodes = parseSimpleModel(model.getSimpleModel());
// 3. 遍历节点:先处理抄送,遇到第一个审批节点就创建任务并 break
for (Map<String, Object> node : nodes) {
if (isCopyNode(node)) {
sendCopyNotifications(instance, node, variables);
continue; // 抄送节点不阻断流程
}
if (isApprovalNode(node)) {
createTask(instance, node, variables);
break; // 只创建第一个审批节点的任务
}
}
// 4. 业务表 approval_status 回写:流程启动 -> IN_PROGRESS
businessStatusCallback.onStarted(instance);
return instance;
}SimpleFlowEngine.completeTask()
@Transactional
public void completeTask(BpmTask task, String action, String opinion) {
if (!"pending".equals(task.getStatus())) {
throw new IllegalStateException("任务已处理,无法重复操作");
}
task.setOpinion(opinion);
if (task.getIsSign() != null && task.getIsSign() == 1) {
// 会签节点:走 completeSignTask 逻辑
completeSignTask(task, action);
} else {
// 普通节点
if ("approve".equals(action) || "agree".equals(action)) {
task.setStatus("completed");
task.setCompleteTime(LocalDateTime.now());
} else if ("reject".equals(action)) {
task.setStatus("rejected");
task.setCompleteTime(LocalDateTime.now());
// 驳回时整个实例置为 rejected
BpmInstance instance = instanceMapper.selectById(task.getInstanceId());
instance.setStatus("rejected");
instance.setEndTime(LocalDateTime.now());
instanceMapper.updateById(instance);
// 发送结果抄送通知
sendResultCopyNotifications(instance, "审批驳回");
// 业务表回写:REJECTED
businessStatusCallback.onTerminal(instance);
}
taskMapper.updateById(task);
// 检查是否所有任务都完成了(没有 pending 任务则实例完成)
checkAndCompleteInstance(task.getInstanceId());
// 通知任务关注者
notifyTaskFollowers(task, "completed");
}
}SimpleFlowEngine.checkAndCompleteInstance()
private void checkAndCompleteInstance(Long instanceId) {
// 查询是否还有 pending 状态的任务
LambdaQueryWrapper<BpmTask> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(BpmTask::getInstanceId, instanceId);
wrapper.in(BpmTask::getStatus, "pending");
long pendingCount = taskMapper.selectCount(wrapper);
if (pendingCount == 0) {
// 没有待办任务,实例完成
BpmInstance instance = instanceMapper.selectById(instanceId);
if (instance != null && !"rejected".equals(instance.getStatus())) {
instance.setStatus("completed");
instance.setEndTime(LocalDateTime.now());
instanceMapper.updateById(instance);
// 发送结果抄送通知
sendResultCopyNotifications(instance, "审批通过");
// 业务表回写:APPROVED
businessStatusCallback.onTerminal(instance);
}
}
}BpmInstanceController 端点一览
基础路径:/api/bpm/instance
| HTTP | 路径 | 参数 | 说明 |
|---|---|---|---|
| GET | /list | userId(可选), status(可选), defKey(可选) | 实例列表 |
| GET | /page | pageNum, pageSize, status(可选), defKey(可选) | 分页查询 |
| GET | /{id} | id | 获取实例详情 |
| POST | /start | Body: StartProcessRequest | 通过模型 ID 发起流程 |
| POST | /startByKey | Body: StartByKeyRequest | 通过 defKey 发起流程 |
| GET | /available | userId(可选) | 获取可发起的流程列表 |
| POST | /cancel/{id} | id, userId(可选) | 取消流程 |
| GET | /{id}/tasks | id | 获取实例的所有任务 |
| GET | /view/{id} | id | 查看实例详情(含任务、审批记录) |
| GET | /flow/{id} | id | 获取流程图数据 |
| POST | /cc/{id} | id, userIds, comment(可选) | 抄送 |
| GET | /cc/{id} | id | 获取抄送列表 |
| POST | /recall/{id} | id, userId | 撤回流程 |
StartProcessRequest 请求体
{
"defId": 1,
"title": "采购审批-办公用品",
"formData": "{\"amount\": 5000, \"item\": \"笔记本\"}",
"businessKey": "purchase_request:100",
"businessType": "purchase_request",
"startUserId": 1,
"startUserName": "张三",
"startDeptId": 100,
"nextAssigneeId": 2,
"nextAssigneeName": "李四",
"variables": {
"amount": 5000
}
}BpmTaskController 端点一览
基础路径:/api/bpm/task
| HTTP | 路径 | 参数 | 说明 |
|---|---|---|---|
| GET | /todo | userId, pageNum, pageSize | 待办任务 |
| GET | /done | userId, pageNum, pageSize | 已办任务 |
| GET | /myStarted | userId, pageNum, pageSize | 我发起的流程 |
| POST | /approve/{id} | id, Body: ApproveRequest | 审批通过 |
| POST | /reject/{id} | id, Body: ApproveRequest | 审批驳回 |
| POST | /delegate/{id} | id, delegateUserId, delegateUserName | 委托(简单版) |
| GET | /{id} | id | 获取任务详情 |
| GET | /detail/{id} | id | 审批详情(含实例、任务列表、审批记录、表单数据) |
ApproveRequest 请求体
{
"opinion": "同意,采购金额合理"
}审批通过流程
// BpmTaskController.java
@PostMapping("/approve/{id}")
public Result<Void> approve(@PathVariable Long id, @RequestBody ApproveRequest request) {
BpmTask task = taskService.getById(id);
if (task == null) {
return Result.error("任务不存在");
}
flowEngine.completeTask(task, "approve", request.getOpinion());
return Result.success("审批成功");
}引擎内部流程:
completeTask(task, "approve", opinion)-> 任务状态置为completedcheckAndCompleteInstance(instanceId)-> 查询是否还有pending任务- 如果没有
pending任务 -> 实例状态置为completed->businessStatusCallback.onTerminal(instance)回写APPROVED - 如果还有
pending任务 -> 流程继续等待其他审批人处理
审批通过后不会自动创建下一节点任务
当前引擎实现中,completeTask 完成当前任务后仅检查是否所有任务完成。不会自动解析下一个审批节点并创建新任务。这意味着 simpleModel 中的节点链路目前主要在 startProcess 时解析,多节点串行审批需要通过其他机制(如在前端发起时指定 nextAssigneeId)来推进。
审批驳回流程
// BpmTaskController.java
@PostMapping("/reject/{id}")
public Result<Void> reject(@PathVariable Long id, @RequestBody ApproveRequest request) {
BpmTask task = taskService.getById(id);
flowEngine.completeTask(task, "reject", request.getOpinion());
return Result.success("已驳回");
}引擎内部流程:
completeTask(task, "reject", opinion)-> 任务状态置为rejected- 实例状态立即置为
rejected,设置endTime sendResultCopyNotifications(instance, "审批驳回")-> 发送结果抄送通知businessStatusCallback.onTerminal(instance)-> 回写REJECTED到业务表

实例状态流转图
startProcess
│
▼
┌─────────┐
│ running │
└────┬────┘
┌─────────┼─────────┐
│ │ │
approve all reject recall
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│completed │ │ rejected │ │ recalled │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
APPROVED REJECTED WITHDRAWN
(业务表回写) (业务表回写) (业务表回写)
前端实现
todo.vue(待办任务页)
待办任务页面展示当前用户需要处理的审批任务:
- 调用
GET /bpm/task/todo?userId=xxx&pageNum=1&pageSize=20获取待办列表 - 每条任务展示:流程标题、节点名称、发起人、发起时间
- 点击「通过」-> 调用
POST /bpm/task/approve/{id}传入审批意见 - 点击「驳回」-> 调用
POST /bpm/task/reject/{id}传入驳回原因 - 点击「详情」-> 跳转到任务详情页,调用
GET /bpm/task/detail/{id}获取完整信息

instance.vue(流程实例页)
流程实例管理页面展示所有流程实例:
- 调用
GET /bpm/instance/page?pageNum=1&pageSize=20获取分页列表 - 支持按状态筛选(
running/completed/rejected/recalled) - 点击「查看」-> 调用
GET /bpm/instance/view/{id}获取实例详情 - 发起人可点击「撤回」-> 调用
POST /bpm/instance/recall/{id}?userId=xxx
操作步骤
1. 发起流程
# 通过模型 ID 发起
curl -X POST http://localhost:52856/api/bpm/instance/start \
-H "Content-Type: application/json" \
-d '{
"defId": 1,
"title": "采购审批-办公用品",
"businessKey": "purchase_request:100",
"businessType": "purchase_request",
"startUserId": 1,
"startUserName": "张三",
"formData": "{\"amount\":5000}"
}'businessKey 格式约定
businessKey 使用 formCode:dataId 格式,例如 purchase_request:100。businessType 存 formCode。这两个字段在 BpmBusinessStatusCallback 中用于定位业务表和数据行。
2. 查看待办
# 获取待办任务
curl "http://localhost:52856/api/bpm/task/todo?userId=2&pageNum=1&pageSize=20"3. 审批通过
# 审批通过
curl -X POST http://localhost:52856/api/bpm/task/approve/1 \
-H "Content-Type: application/json" \
-d '{"opinion": "同意,金额合理"}'4. 审批驳回
# 审批驳回
curl -X POST http://localhost:52856/api/bpm/task/reject/1 \
-H "Content-Type: application/json" \
-d '{"opinion": "金额超标,请重新评估"}'5. 查看流程详情
# 查看实例详情(含任务列表、审批记录)
curl "http://localhost:52856/api/bpm/instance/view/1"6. 撤回流程
# 发起人撤回(仅限无任务完成前)
curl -X POST "http://localhost:52856/api/bpm/instance/recall/1?userId=1"常见问题
任务已处理无法重复操作
completeTask() 方法开头检查 task.getStatus() 是否为 "pending",如果不是则抛出 IllegalStateException("任务已处理,无法重复操作")。前端应在审批后禁用按钮,防止重复提交。
驳回后整个流程立即终止
当前实现中,任何节点驳回都会将整个实例置为 rejected,不会退回到上一节点。如果需要「驳回到指定节点」功能,需要扩展 rejectHandler 逻辑。
查看已办任务
调用 GET /bpm/task/done?userId=xxx 获取当前用户已处理的任务列表,返回的任务状态为 completed / rejected / transferred 等。
