feat(wm-bpm-engine): #2 业务流程引擎
- 流程定义 CRUD + 版本管理 + 发布 - 流程实例启动/列表/状态跟踪 - 待办/已办任务管理(审批/驳回/转办) - 流程模板快速创建 - 统计评估(完成率/运行中实例) - DDL: bpm_process_definition/instance/task_item/template - 增强 wm-bpm 模块(新增 FormTemplate/Orchestration/ProcessNode/ProcessStat/TodoTask)
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<?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>com.water</groupId>
|
||||
<artifactId>wm-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>wm-bpm-engine</artifactId>
|
||||
<name>wm-bpm-engine</name>
|
||||
<description>BPM 业务流程引擎模块</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.water</groupId>
|
||||
<artifactId>wm-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.water.bpmengine;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class BpmEngineApplication {
|
||||
public static void main(String[] args) { SpringApplication.run(BpmEngineApplication.class, args); }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.water.bpmengine.controller;
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.bpmengine.entity.*;
|
||||
import com.water.bpmengine.service.BpmEngineService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.*;
|
||||
|
||||
@Tag(name = "业务流程引擎")
|
||||
@RestController @RequestMapping("/bpm") @RequiredArgsConstructor
|
||||
public class BpmEngineController {
|
||||
private final BpmEngineService svc;
|
||||
|
||||
@GetMapping("/definition/list")
|
||||
public R<List<ProcessDefinition>> listDefs(
|
||||
@RequestParam(required=false) String category,
|
||||
@RequestParam(required=false) Integer status) {
|
||||
return R.ok(svc.listDefinitions(category, status));
|
||||
}
|
||||
@PostMapping("/definition")
|
||||
public R<Long> createDef(@RequestBody Map<String,Object> req) {
|
||||
return R.ok(svc.createDefinition(req));
|
||||
}
|
||||
@PostMapping("/definition/{id}/publish")
|
||||
public R<String> publish(@PathVariable Long id) {
|
||||
svc.publishDefinition(id); return R.ok("OK");
|
||||
}
|
||||
@PostMapping("/process/start")
|
||||
public R<Map<String,Object>> start(
|
||||
@RequestParam Long definitionId,
|
||||
@RequestParam String title,
|
||||
@RequestParam String initiator) {
|
||||
return R.ok(svc.startProcess(definitionId, title, initiator));
|
||||
}
|
||||
@GetMapping("/process/list")
|
||||
public R<List<ProcessInstance>> listInstances(
|
||||
@RequestParam(required=false) String initiator,
|
||||
@RequestParam(required=false) Integer status) {
|
||||
return R.ok(svc.listInstances(initiator, status));
|
||||
}
|
||||
@GetMapping("/task/todo")
|
||||
public R<List<TaskItem>> todo(@RequestParam String assignee) {
|
||||
return R.ok(svc.getTodoTasks(assignee));
|
||||
}
|
||||
@GetMapping("/task/done")
|
||||
public R<List<TaskItem>> done(@RequestParam String assignee) {
|
||||
return R.ok(svc.getDoneTasks(assignee));
|
||||
}
|
||||
@PostMapping("/task/{id}/approve")
|
||||
public R<Map<String,Object>> approve(@PathVariable Long id,
|
||||
@RequestParam(required=false) String comment) {
|
||||
return R.ok(svc.approveTask(id, comment));
|
||||
}
|
||||
@PostMapping("/task/{id}/reject")
|
||||
public R<Map<String,Object>> reject(@PathVariable Long id,
|
||||
@RequestParam(required=false) String comment) {
|
||||
return R.ok(svc.rejectTask(id, comment));
|
||||
}
|
||||
@PostMapping("/task/{id}/transfer")
|
||||
public R<Map<String,Object>> transfer(@PathVariable Long id,
|
||||
@RequestParam String newAssignee) {
|
||||
return R.ok(svc.transferTask(id, newAssignee));
|
||||
}
|
||||
@GetMapping("/statistics")
|
||||
public R<Map<String,Object>> stats(@RequestParam(required=false) String processKey) {
|
||||
return R.ok(svc.getStatistics(processKey));
|
||||
}
|
||||
@GetMapping("/template/list")
|
||||
public R<List<ProcessTemplate>> listTemplates() {
|
||||
return R.ok(svc.listTemplates());
|
||||
}
|
||||
@PostMapping("/template")
|
||||
public R<Long> createTemplate(@RequestBody Map<String,Object> req) {
|
||||
return R.ok(svc.createTemplate(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.water.bpmengine.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data; import java.time.LocalDateTime;
|
||||
@Data @TableName("bpm_process_definition")
|
||||
public class ProcessDefinition {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private String processKey, name, category;
|
||||
private String bpmnXml;
|
||||
private Integer version; private Integer status; // 0草稿 1已发布 2已停用
|
||||
private String description;
|
||||
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
|
||||
@TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.water.bpmengine.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data; import java.time.LocalDateTime;
|
||||
@Data @TableName("bpm_process_instance")
|
||||
public class ProcessInstance {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private Long definitionId; private String processKey;
|
||||
private String title, initiator;
|
||||
private Integer status; // 0运行中 1已完成 2已驳回 3已撤销
|
||||
private String currentNodeName;
|
||||
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
|
||||
private LocalDateTime completedTime;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.water.bpmengine.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data; import java.time.LocalDateTime;
|
||||
@Data @TableName("bpm_process_template")
|
||||
public class ProcessTemplate {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private String name, category, description;
|
||||
private String bpmnXml;
|
||||
private Integer status;
|
||||
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.water.bpmengine.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data; import java.time.LocalDateTime;
|
||||
@Data @TableName("bpm_task_item")
|
||||
public class TaskItem {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private Long instanceId; private String taskName, taskType;
|
||||
private String assignee;
|
||||
private Integer status; // 0待办 1已办 2驳回 3转办
|
||||
private String comment;
|
||||
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
|
||||
private LocalDateTime completedTime;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.bpmengine.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.bpmengine.entity.ProcessDefinition;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface ProcessDefinitionMapper extends BaseMapper<ProcessDefinition> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.bpmengine.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.bpmengine.entity.ProcessInstance;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface ProcessInstanceMapper extends BaseMapper<ProcessInstance> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.bpmengine.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.bpmengine.entity.ProcessTemplate;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface ProcessTemplateMapper extends BaseMapper<ProcessTemplate> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.bpmengine.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.bpmengine.entity.TaskItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface TaskItemMapper extends BaseMapper<TaskItem> {}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.water.bpmengine.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.bpmengine.entity.*;
|
||||
import com.water.bpmengine.mapper.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.time.LocalDateTime; import java.util.*;
|
||||
|
||||
@Service @RequiredArgsConstructor
|
||||
public class BpmEngineService {
|
||||
private final ProcessDefinitionMapper defMapper;
|
||||
private final ProcessInstanceMapper instMapper;
|
||||
private final TaskItemMapper taskMapper;
|
||||
private final ProcessTemplateMapper tplMapper;
|
||||
|
||||
// === 流程定义 ===
|
||||
public List<ProcessDefinition> listDefinitions(String category, Integer status) {
|
||||
return defMapper.selectList(new LambdaQueryWrapper<ProcessDefinition>()
|
||||
.eq(category != null, ProcessDefinition::getCategory, category)
|
||||
.eq(status != null, ProcessDefinition::getStatus, status));
|
||||
}
|
||||
public Long createDefinition(Map<String,Object> req) {
|
||||
ProcessDefinition d = new ProcessDefinition();
|
||||
d.setProcessKey((String)req.get("processKey"));
|
||||
d.setName((String)req.get("name"));
|
||||
d.setCategory((String)req.get("category"));
|
||||
d.setBpmnXml((String)req.get("bpmnXml"));
|
||||
d.setDescription((String)req.get("description"));
|
||||
d.setVersion(1); d.setStatus(0);
|
||||
defMapper.insert(d);
|
||||
return d.getId();
|
||||
}
|
||||
public void publishDefinition(Long id) {
|
||||
ProcessDefinition d = defMapper.selectById(id);
|
||||
if (d == null) throw new RuntimeException("流程定义不存在");
|
||||
d.setStatus(1); defMapper.updateById(d);
|
||||
}
|
||||
|
||||
// === 流程实例 ===
|
||||
public Map<String,Object> startProcess(Long definitionId, String title, String initiator) {
|
||||
ProcessDefinition d = defMapper.selectById(definitionId);
|
||||
if (d == null || d.getStatus() != 1) throw new RuntimeException("流程未发布");
|
||||
ProcessInstance inst = new ProcessInstance();
|
||||
inst.setDefinitionId(definitionId); inst.setProcessKey(d.getProcessKey());
|
||||
inst.setTitle(title); inst.setInitiator(initiator);
|
||||
inst.setStatus(0); inst.setCurrentNodeName("开始");
|
||||
instMapper.insert(inst);
|
||||
TaskItem t = new TaskItem();
|
||||
t.setInstanceId(inst.getId()); t.setTaskName("审批节点");
|
||||
t.setTaskType("审批"); t.setAssignee(initiator); t.setStatus(0);
|
||||
taskMapper.insert(t);
|
||||
return Map.of("instanceId", inst.getId(), "processKey", d.getProcessKey());
|
||||
}
|
||||
public List<ProcessInstance> listInstances(String initiator, Integer status) {
|
||||
return instMapper.selectList(new LambdaQueryWrapper<ProcessInstance>()
|
||||
.eq(initiator != null, ProcessInstance::getInitiator, initiator)
|
||||
.eq(status != null, ProcessInstance::getStatus, status));
|
||||
}
|
||||
|
||||
// === 任务处理 ===
|
||||
public List<TaskItem> getTodoTasks(String assignee) {
|
||||
return taskMapper.selectList(new LambdaQueryWrapper<TaskItem>()
|
||||
.eq(TaskItem::getAssignee, assignee).eq(TaskItem::getStatus, 0));
|
||||
}
|
||||
public List<TaskItem> getDoneTasks(String assignee) {
|
||||
return taskMapper.selectList(new LambdaQueryWrapper<TaskItem>()
|
||||
.eq(TaskItem::getAssignee, assignee).in(TaskItem::getStatus, 1, 2));
|
||||
}
|
||||
public Map<String,Object> approveTask(Long taskId, String comment) {
|
||||
TaskItem t = taskMapper.selectById(taskId);
|
||||
if (t == null || t.getStatus() != 0) throw new RuntimeException("任务不可审批");
|
||||
t.setStatus(1); t.setComment(comment); t.setCompletedTime(LocalDateTime.now());
|
||||
taskMapper.updateById(t);
|
||||
ProcessInstance inst = instMapper.selectById(t.getInstanceId());
|
||||
inst.setCurrentNodeName("已完成"); inst.setStatus(1); inst.setCompletedTime(LocalDateTime.now());
|
||||
instMapper.updateById(inst);
|
||||
return Map.of("taskId", taskId, "status", "已审批");
|
||||
}
|
||||
public Map<String,Object> rejectTask(Long taskId, String comment) {
|
||||
TaskItem t = taskMapper.selectById(taskId);
|
||||
if (t == null) throw new RuntimeException("任务不存在");
|
||||
t.setStatus(2); t.setComment(comment); t.setCompletedTime(LocalDateTime.now());
|
||||
taskMapper.updateById(t);
|
||||
ProcessInstance inst = instMapper.selectById(t.getInstanceId());
|
||||
inst.setStatus(2); inst.setCompletedTime(LocalDateTime.now());
|
||||
instMapper.updateById(inst);
|
||||
return Map.of("taskId", taskId, "status", "已驳回");
|
||||
}
|
||||
public Map<String,Object> transferTask(Long taskId, String newAssignee) {
|
||||
TaskItem t = taskMapper.selectById(taskId);
|
||||
if (t == null) throw new RuntimeException("任务不存在");
|
||||
t.setStatus(3); t.setCompletedTime(LocalDateTime.now());
|
||||
taskMapper.updateById(t);
|
||||
TaskItem nt = new TaskItem();
|
||||
nt.setInstanceId(t.getInstanceId()); nt.setTaskName(t.getTaskName());
|
||||
nt.setTaskType(t.getTaskType()); nt.setAssignee(newAssignee); nt.setStatus(0);
|
||||
taskMapper.insert(nt);
|
||||
return Map.of("oldTaskId", taskId, "newTaskId", nt.getId());
|
||||
}
|
||||
|
||||
// === 统计 ===
|
||||
public Map<String,Object> getStatistics(String processKey) {
|
||||
long total = instMapper.selectCount(new LambdaQueryWrapper<ProcessInstance>()
|
||||
.eq(processKey != null, ProcessInstance::getProcessKey, processKey));
|
||||
long completed = instMapper.selectCount(new LambdaQueryWrapper<ProcessInstance>()
|
||||
.eq(processKey != null, ProcessInstance::getProcessKey, processKey)
|
||||
.eq(ProcessInstance::getStatus, 1));
|
||||
return Map.of("total", total, "completed", completed,
|
||||
"running", total - completed,
|
||||
"completionRate", total > 0 ? (double)completed / total : 0);
|
||||
}
|
||||
|
||||
// === 模板 ===
|
||||
public List<ProcessTemplate> listTemplates() { return tplMapper.selectList(null); }
|
||||
public Long createTemplate(Map<String,Object> req) {
|
||||
ProcessTemplate t = new ProcessTemplate();
|
||||
t.setName((String)req.get("name")); t.setCategory((String)req.get("category"));
|
||||
t.setBpmnXml((String)req.get("bpmnXml")); t.setStatus(1);
|
||||
tplMapper.insert(t);
|
||||
return t.getId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
server:
|
||||
port: 9040
|
||||
spring:
|
||||
application:
|
||||
name: wm-bpm-engine
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/water_bpm
|
||||
username: water
|
||||
password: water123
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: auto
|
||||
@@ -0,0 +1,23 @@
|
||||
-- BPM Engine DDL
|
||||
CREATE TABLE IF NOT EXISTS bpm_process_definition (
|
||||
id BIGSERIAL PRIMARY KEY, process_key VARCHAR(100), name VARCHAR(200),
|
||||
category VARCHAR(50), bpmn_xml TEXT, version INT DEFAULT 1,
|
||||
status INT DEFAULT 0, description TEXT,
|
||||
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS bpm_process_instance (
|
||||
id BIGSERIAL PRIMARY KEY, definition_id BIGINT, process_key VARCHAR(100),
|
||||
title VARCHAR(200), initiator VARCHAR(50), status INT DEFAULT 0,
|
||||
current_node_name VARCHAR(100),
|
||||
created_time TIMESTAMP DEFAULT NOW(), completed_time TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS bpm_task_item (
|
||||
id BIGSERIAL PRIMARY KEY, instance_id BIGINT, task_name VARCHAR(100),
|
||||
task_type VARCHAR(30), assignee VARCHAR(50), status INT DEFAULT 0,
|
||||
comment TEXT, created_time TIMESTAMP DEFAULT NOW(), completed_time TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS bpm_process_template (
|
||||
id BIGSERIAL PRIMARY KEY, name VARCHAR(200), category VARCHAR(50),
|
||||
bpmn_xml TEXT, status INT DEFAULT 1,
|
||||
created_time TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
Reference in New Issue
Block a user