RuoYi-Vue工作流构建:可视化流程设计
引言:企业级工作流的迫切需求
在现代企业信息化建设中,业务流程管理(BPM)已成为提升组织效率的核心引擎。传统的纸质审批流程耗时耗力,信息传递不畅,流程监控困难,严重制约了企业的数字化转型进程。RuoYi-Vue作为一款优秀的前后端分离快速开发框架,为企业提供了构建可视化工作流系统的完整解决方案。
通过本文,您将掌握:
- RuoYi-Vue工作流架构设计与实现原理
- 可视化流程设计器的集成与应用
- 基于BPMN 2.0标准的流程建模方法
- 动态表单与流程引擎的无缝集成
- 实时流程监控与性能优化策略
工作流技术栈选型与架构设计
核心组件选择
技术选型对比表
| 技术方案 | 优势 | 适用场景 | 集成复杂度 |
|---|---|---|---|
| Flowable | 功能全面,社区活跃 | 复杂业务流程 | 中等 |
| Activiti | 成熟稳定,文档丰富 | 传统企业应用 | 低 |
| Camunda | 性能优异,扩展性强 | 高并发场景 | 高 |
| 自研引擎 | 定制灵活,轻量级 | 简单审批流 | 低 |
可视化流程设计器集成
BPMN.js集成配置
// 在ruoyi-ui中集成BPMN设计器
import BpmnModeler from 'bpmn-js/lib/Modeler'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
export default {
data() {
return {
bpmnModeler: null,
canvas: null,
currentXML: ''
}
},
mounted() {
this.initBpmnModeler()
},
methods: {
initBpmnModeler() {
this.bpmnModeler = new BpmnModeler({
container: '#canvas',
keyboard: { bindTo: document },
additionalModules: [
// 自定义模块扩展
]
})
this.loadSampleDiagram()
},
async loadSampleDiagram() {
try {
const { data } = await this.axios.get('/api/bpmn/template')
const result = await this.bpmnModeler.importXML(data)
const { warnings } = result
if (warnings.length) {
console.warn('BPMN导入警告:', warnings)
}
this.canvas = this.bpmnModeler.get('canvas')
this.canvas.zoom('fit-viewport')
} catch (err) {
console.error('BPMN导入错误:', err)
}
},
async saveDiagram() {
try {
const { xml } = await this.bpmnModeler.saveXML({ format: true })
this.currentXML = xml
await this.axios.post('/api/bpmn/deploy', {
bpmnXml: xml,
processName: this.processName
})
this.$message.success('流程部署成功')
} catch (error) {
this.$message.error('流程保存失败')
}
}
}
}
自定义Palette扩展
// 自定义工具栏组件
export default {
methods: {
createCustomPalette() {
const palette = this.bpmnModeler.get('palette')
palette.registerProvider('custom', function() {
return {
getPaletteEntries: function(element) {
return {
'custom-task': {
group: 'activity',
className: 'bpmn-icon-task custom-task',
title: '自定义任务',
action: {
dragstart: function(event) {
// 拖拽开始处理
},
click: function(event) {
// 点击处理
}
}
}
}
}
}
})
}
}
}
后端流程引擎实现
Flowable集成配置
// Spring Boot配置类
@Configuration
public class FlowableConfig {
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration(
DataSource dataSource, PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setDataSource(dataSource);
config.setTransactionManager(transactionManager);
config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
config.setAsyncExecutorActivate(true);
config.setHistoryLevel(HistoryLevel.AUDIT);
// 自定义行为扩展
config.setActivityBehaviorFactory(new CustomActivityBehaviorFactory());
return config;
}
@Bean
public ProcessEngine processEngine(SpringProcessEngineConfiguration config) {
return config.buildProcessEngine();
}
@Bean
public RepositoryService repositoryService(ProcessEngine processEngine) {
return processEngine.getRepositoryService();
}
@Bean
public RuntimeService runtimeService(ProcessEngine processEngine) {
return processEngine.getRuntimeService();
}
@Bean
public TaskService taskService(ProcessEngine processEngine) {
return processEngine.getTaskService();
}
}
流程部署服务
@Service
public class BpmnDeploymentService {
@Autowired
private RepositoryService repositoryService;
/**
* 部署BPMN流程定义
*/
public Deployment deployProcess(String processName, String bpmnXml) {
Deployment deployment = repositoryService.createDeployment()
.name(processName)
.addString(processName + ".bpmn20.xml", bpmnXml)
.deploy();
// 记录部署日志
log.info("流程部署成功: {}, 部署ID: {}", processName, deployment.getId());
return deployment;
}
/**
* 启动流程实例
*/
public ProcessInstance startProcess(String processDefinitionKey,
String businessKey,
Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(
processDefinitionKey, businessKey, variables);
}
/**
* 查询用户任务
*/
public List<Task> getUserTasks(String userId) {
return taskService.createTaskQuery()
.taskAssignee(userId)
.orderByTaskCreateTime().desc()
.list();
}
}
动态表单系统设计
表单JSON Schema定义
{
"formId": "leave_approval",
"formName": "请假审批表单",
"version": "1.0",
"fields": [
{
"fieldId": "leaveType",
"fieldName": "请假类型",
"fieldType": "select",
"required": true,
"options": [
{"label": "年假", "value": "annual"},
{"label": "病假", "value": "sick"},
{"label": "事假", "value": "personal"}
],
"rules": [{ "required": true, "message": "请选择请假类型" }]
},
{
"fieldId": "startDate",
"fieldName": "开始时间",
"fieldType": "datetime",
"required": true
},
{
"fieldId": "endDate",
"fieldName": "结束时间",
"fieldType": "datetime",
"required": true
},
{
"fieldId": "reason",
"fieldName": "请假事由",
"fieldType": "textarea",
"required": true,
"maxLength": 500
}
],
"layout": {
"type": "grid",
"columns": 2,
"gutter": 20
}
}
Vue动态表单渲染器
<template>
<el-form :model="formData" :rules="formRules" ref="dynamicForm">
<el-row :gutter="formConfig.layout.gutter">
<el-col
v-for="field in formConfig.fields"
:key="field.fieldId"
:span="field.span || 24/formConfig.layout.columns">
<el-form-item
:label="field.fieldName"
:prop="field.fieldId"
:required="field.required">
<component
:is="getFieldComponent(field.fieldType)"
v-model="formData[field.fieldId]"
v-bind="getFieldProps(field)"
:placeholder="`请输入${field.fieldName}`">
<template v-if="field.fieldType === 'select'">
<el-option
v-for="option in field.options"
:key="option.value"
:label="option.label"
:value="option.value">
</el-option>
</template>
</component>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
export default {
props: {
formConfig: {
type: Object,
required: true
},
formData: {
type: Object,
default: () => ({})
}
},
computed: {
formRules() {
const rules = {}
this.formConfig.fields.forEach(field => {
if (field.rules) {
rules[field.fieldId] = field.rules
} else if (field.required) {
rules[field.fieldId] = [
{ required: true, message: `${field.fieldName}不能为空` }
]
}
})
return rules
}
},
methods: {
getFieldComponent(fieldType) {
const componentMap = {
'input': 'el-input',
'textarea': 'el-input',
'select': 'el-select',
'datetime': 'el-date-picker',
'number': 'el-input-number'
}
return componentMap[fieldType] || 'el-input'
},
getFieldProps(field) {
const props = {}
switch (field.fieldType) {
case 'textarea':
props.type = 'textarea'
props.rows = 4
break
case 'datetime':
props.type = 'datetime'
break
case 'number':
props.type = 'number'
break
}
return { ...field, ...props }
}
}
}
</script>
流程节点与业务逻辑集成
自定义任务监听器
@Component
public class CustomTaskListener implements TaskListener {
@Autowired
private SysUserService userService;
@Autowired
private MessageService messageService;
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
String taskDefinitionKey = delegateTask.getTaskDefinitionKey();
switch (eventName) {
case EVENTNAME_CREATE:
handleTaskCreate(delegateTask, taskDefinitionKey);
break;
case EVENTNAME_ASSIGNMENT:
handleTaskAssignment(delegateTask);
break;
case EVENTNAME_COMPLETE:
handleTaskComplete(delegateTask);
break;
}
}
private void handleTaskCreate(DelegateTask delegateTask, String taskKey) {
// 根据任务Key设置处理人
if ("manager_approval".equals(taskKey)) {
String departmentId = (String) delegateTask.getVariable("departmentId");
List<SysUser> managers = userService.selectManagersByDept(departmentId);
if (!managers.isEmpty()) {
delegateTask.setAssignee(managers.get(0).getUserName());
}
}
// 发送任务通知
messageService.sendTaskNotification(
delegateTask.getAssignee(),
delegateTask.getName(),
delegateTask.getProcessInstanceId()
);
}
private void handleTaskAssignment(DelegateTask delegateTask) {
// 任务分配处理逻辑
log.info("任务分配给: {}", delegateTask.getAssignee());
}
private void handleTaskComplete(DelegateTask delegateTask) {
// 任务完成处理逻辑
Map<String, Object> variables = delegateTask.getVariables();
log.info("任务完成: {}, 变量: {}", delegateTask.getName(), variables);
}
}
服务任务实现
@Component
public class ApprovalServiceTask implements JavaDelegate {
@Autowired
private ApprovalRecordService approvalRecordService;
@Override
public void execute(DelegateExecution execution) {
String businessKey = execution.getProcessInstanceBusinessKey();
String taskName = execution.getCurrentActivityName();
String assignee = (String) execution.getVariable("approver");
// 记录审批操作
ApprovalRecord record = new ApprovalRecord();
record.setBusinessKey(businessKey);
record.setTaskName(taskName);
record.setApprover(assignee);
record.setApprovalTime(new Date());
record.setApprovalResult((String) execution.getVariable("approvalResult"));
record.setComments((String) execution.getVariable("comments"));
approvalRecordService.insertApprovalRecord(record);
// 更新业务状态
updateBusinessStatus(execution, record.getApprovalResult());
}
private void updateBusinessStatus(DelegateExecution execution, String result) {
String businessKey = execution.getProcessInstanceBusinessKey();
String status = "APPROVED".equals(result) ? "approved" : "rejected";
// 根据业务类型更新不同表的状态
String businessType = (String) execution.getVariable("businessType");
switch (businessType) {
case "leave":
leaveService.updateStatus(businessKey, status);
break;
case "expense":
expenseService.updateStatus(businessKey, status);
break;
case "purchase":
purchaseService.updateStatus(businessKey, status);
break;
}
}
}
高级特性与最佳实践
流程版本控制策略
@Service
public class ProcessVersionService {
@Autowired
private RepositoryService repositoryService;
/**
* 获取流程定义的最新版本
*/
public ProcessDefinition getLatestProcessDefinition(String processKey) {
return repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(processKey)
.latestVersion()
.singleResult();
}
/**
* 流程版本迁移
*/
public void migrateProcessInstances(String oldProcessDefinitionId,
String newProcessDefinitionId) {
List<ProcessInstance> instances = runtimeService
.createProcessInstanceQuery()
.processDefinitionId(oldProcessDefinitionId)
.list();
for (ProcessInstance instance : instances) {
runtimeService.createProcessInstanceMigrationBuilder()
.migrateToProcessDefinition(newProcessDefinitionId)
.migrate(instance.getId());
}
}
}
性能优化配置
# application-flowable.yml
flowable:
async-executor:
enabled: true
core-pool-size: 10
max-pool-size: 50
queue-size: 100
seconds-to-wait-on-shutdown: 60
history-level: audit
database-schema-update: true
job-executor-activate: true
mail-server:
default-from: noreply@company.com
host: smtp.company.com
port: 587
username: ${MAIL_USERNAME}
password: ${MAIL_PASSWORD}
监控与运维
流程实例监控看板
<template>
<div class="process-monitor">
<el-row :gutter="20">
<el-col :span="6" v-for="metric in metrics" :key="metric.title">
<el-card>
<div class="metric-card">
<div class="metric-value">{{ metric.value }}</div>
<div class="metric-title">{{ metric.title }}</div>
<div class="metric-trend" :class="metric.trend">
<i :class="trendIcon(metric.trend)"></i>
{{ metric.rate }}%
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-table :data="processInstances" style="width: 100%">
<el-table-column prop="processDefinitionName" label="流程名称"></el-table-column>
<el-table-column prop="businessKey" label="业务编号"></el-table-column>
<el-table-column prop="startTime" label="开始时间"></el-table-column>
<el-table-column prop="currentActivity" label="当前环节"></el-table-column>
<el-table-column prop="duration" label="耗时"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="viewDetails(scope.row)" type="text">详情</el-button>
<el-button @click="terminateProcess(scope.row)" type="text" v-if="scope.row.suspended">终止</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
metrics: [
{ title: '运行中流程', value: 128, trend: 'up', rate: 12 },
{ title: '已完成流程', value: 456, trend: 'up', rate: 8 },
{ title: '异常流程', value: 5, trend: 'down', rate: -20 },
{ title: '平均耗时', value: '2.5h', trend: 'down', rate: -5 }
],
processInstances: []
}
},
mounted() {
this.loadProcessInstances()
this.startAutoRefresh()
},
methods: {
async loadProcessInstances() {
const { data } = await this.axios.get('/api/process/instances')
this.processInstances = data
},
startAutoRefresh() {
setInterval(() => {
this.loadProcessInstances()
}, 30000) // 30秒刷新一次
},
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



