1. 项目背景与需求分析
在当今企业级应用开发中,工作流引擎已成为不可或缺的基础设施。Flowable作为Activiti分支发展而来的轻量级工作流引擎,以其高性能和易用性在Java生态中占据重要地位。而若依(RuoYi)作为国内流行的前后端分离快速开发框架,基于Spring Boot和Vue.js技术栈,为开发者提供了完善的基础功能模块。
将Flowable集成到若依Vue2前后端分离项目中,主要解决以下业务场景需求:
- 需要流程审批的业务系统(如OA、ERP等)
- 复杂业务状态需要可视化配置流转路径
- 希望复用若依已有权限体系与Flowable进行深度整合
- 需要在前端可视化查看和操作流程实例
实际开发中发现,虽然网上有各种Flowable集成方案,但针对若依框架的特殊性(如权限体系、模块化设计等)的完整解决方案并不多见。本文将基于最新稳定版本(若依3.8.6 + Flowable 6.7.2)进行详细说明。
2. 环境准备与技术栈确认
2.1 版本兼容性验证
在开始集成前,必须确认技术栈版本匹配:
- 若依Vue2版本要求:
- JDK 1.8+(实测JDK17存在兼容性问题)
- Spring Boot 2.5.x
- MyBatis 3.5.x
- Flowable适配版本:
- 6.7.x系列(与Spring Boot 2.5.x兼容性最佳)
- 注意避免使用7.x版本(需要Spring Boot 3.x支持)
2.2 数据库准备
Flowable需要额外的数据库表支持,建议新建单独schema:
sql复制CREATE SCHEMA `ruoyi_flowable` DEFAULT CHARACTER SET utf8mb4;
若依原有数据库保持不变,这种隔离方案有利于后期维护。
3. 后端集成关键步骤
3.1 Maven依赖配置
在ruoyi-admin模块的pom.xml中添加核心依赖:
xml复制<!-- Flowable核心 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 若依已有MyBatis版本冲突解决 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
特别注意:必须排除Flowable自带的MyBatis依赖,否则会与若依原有版本冲突导致启动失败。
3.2 多数据源配置
在application.yml中新增Flowable数据源配置:
yaml复制# 主数据源(若依原有)
spring:
datasource:
ruoyi:
url: jdbc:mysql://localhost:3306/ry?useSSL=false
username: root
password: password
# Flowable数据源
flowable:
datasource:
url: jdbc:mysql://localhost:3306/ruoyi_flowable?useSSL=false
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
创建自定义配置类解决事务管理冲突:
java复制@Configuration
public class FlowableConfig {
@Bean
@ConfigurationProperties(prefix = "flowable.datasource")
public DataSource flowableDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public PlatformTransactionManager flowableTransactionManager() {
return new DataSourceTransactionManager(flowableDataSource());
}
}
3.3 权限体系整合
若依的Shiro权限需要与Flowable的IDM模块对接。重写默认身份服务:
java复制public class RuoYiIdentityServiceImpl extends DefaultIdentityServiceImpl {
@Override
public User createNewUser(String userId) {
// 关联若依用户体系
SysUser sysUser = sysUserService.selectUserByUserName(userId);
if (sysUser == null) {
throw new RuntimeException("用户不存在");
}
User user = super.createNewUser(userId);
user.setFirstName(sysUser.getNickName());
user.setLastName("");
return user;
}
// 其他方法重写...
}
在启动类中替换默认服务:
java复制@SpringBootApplication(exclude = {
org.flowable.spring.boot.idm.IdmEngineServicesAutoConfiguration.class
})
public class RuoYiApplication {
public static void main(String[] args) {
SpringApplication.run(RuoYiApplication.class, args);
}
@Bean
public IdmEngineConfigurationConfigurer idmEngineConfigurer() {
return engineConfiguration -> {
engineConfiguration.setIdentityService(new RuoYiIdentityServiceImpl());
};
}
}
4. 前端集成方案
4.1 流程设计器集成
使用bpmn-js作为流程设计器核心:
bash复制npm install bpmn-js --save
npm install @babel/plugin-proposal-optional-chaining --save-dev
创建独立的流程设计器组件:
vue复制<template>
<div class="bpmn-container">
<div ref="canvas" class="canvas"></div>
<div class="properties-panel" ref="properties"></div>
</div>
</template>
<script>
import BpmnModeler from 'bpmn-js/lib/Modeler'
import propertiesPanelModule from 'bpmn-js-properties-panel'
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
export default {
mounted() {
this.modeler = new BpmnModeler({
container: this.$refs.canvas,
propertiesPanel: {
parent: this.$refs.properties
},
additionalModules: [
propertiesPanelModule,
propertiesProviderModule
]
})
this.loadSampleDiagram()
},
methods: {
async loadSampleDiagram() {
try {
const res = await this.$http.get('/modeler/blank')
const { xml } = res.data
await this.modeler.importXML(xml)
} catch (err) {
console.error('Error loading diagram', err)
}
}
}
}
</script>
<style scoped>
.bpmn-container {
display: flex;
height: 100%;
}
.canvas {
flex: 1;
height: 100vh;
}
.properties-panel {
width: 300px;
}
</style>
4.2 流程状态可视化
使用d3.js渲染流程执行路径:
vue复制<template>
<div ref="graphContainer" class="flow-graph"></div>
</template>
<script>
import * as d3 from 'd3'
export default {
props: ['processInstanceId'],
data() {
return {
svg: null,
width: 800,
height: 600
}
},
mounted() {
this.initGraph()
this.loadProcessData()
},
methods: {
initGraph() {
this.svg = d3.select(this.$refs.graphContainer)
.append('svg')
.attr('width', this.width)
.attr('height', this.height)
},
async loadProcessData() {
const { data } = await this.$http.get(`/flowable/process/${this.processInstanceId}/diagram`)
this.renderGraph(data)
},
renderGraph(data) {
// 实现D3.js节点和连线绘制逻辑
// ...
}
}
}
</script>
5. 核心业务功能实现
5.1 动态表单集成方案
创建表单定义表:
sql复制CREATE TABLE `wf_form_definition` (
`form_id` bigint(20) NOT NULL AUTO_INCREMENT,
`form_key` varchar(64) NOT NULL,
`form_name` varchar(128) NOT NULL,
`form_json` longtext NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`form_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实现表单与流程节点的绑定:
java复制@PostMapping("/deploy")
public AjaxResult deployWithForm(
@RequestParam String processKey,
@RequestParam Long formId,
@RequestParam MultipartFile file) throws IOException {
// 1. 部署流程定义
Deployment deployment = repositoryService.createDeployment()
.addInputStream(file.getOriginalFilename(), file.getInputStream())
.key(processKey)
.deploy();
// 2. 关联表单定义
WfFormDefinition form = formMapper.selectById(formId);
BpmnModel model = repositoryService.getBpmnModel(deployment.getId());
UserTask startTask = (UserTask) model.getMainProcess()
.getFlowElement("startEvent");
startTask.setFormKey(form.getFormKey());
repositoryService.saveModel(model.getModel());
return AjaxResult.success(deployment.getId());
}
5.2 审批功能实现
扩展若依的待办事项服务:
java复制@Service
public class FlowableTaskServiceImpl implements FlowableTaskService {
@Autowired
private TaskService taskService;
@Autowired
private SysUserService userService;
public List<TaskVo> selectTodoList(TaskQueryDto queryDto) {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(queryDto.getUserId())
.orderByTaskCreateTime().desc()
.list();
return tasks.stream().map(task -> {
TaskVo vo = new TaskVo();
vo.setTaskId(task.getId());
vo.setName(task.getName());
// 关联业务数据
String businessKey = task.getBusinessKey();
if (StringUtils.isNotBlank(businessKey)) {
// 查询关联业务数据...
}
return vo;
}).collect(Collectors.toList());
}
}
6. 部署与性能优化
6.1 生产环境配置建议
在application-prod.yml中添加:
yaml复制flowable:
async-executor-activate: true
async-history-enabled: true
history-level: audit
database-schema-update: false
mail-server-host: smtp.exmail.qq.com
mail-server-port: 465
6.2 历史数据归档方案
创建定时任务清理历史数据:
java复制@Scheduled(cron = "0 0 3 * * ?")
public void archiveHistoricData() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, -3);
Date beforeDate = calendar.getTime();
historyService.createHistoricProcessInstanceQuery()
.finishedBefore(beforeDate)
.list()
.forEach(instance -> {
// 归档到历史表
archiveService.archiveInstance(instance.getId());
// 清理原数据
historyService.deleteHistoricProcessInstance(instance.getId());
});
}
7. 常见问题解决方案
7.1 启动时报错:MyBatis版本冲突
典型错误信息:
code复制java.lang.NoSuchMethodError: org.apache.ibatis.session.Configuration.getDefaultScriptingLanguageInstance()...
解决方案:
- 确保正确排除Flowable的MyBatis依赖
- 在pom.xml中显式指定若依使用的MyBatis版本
7.2 流程设计器中文乱码
在vue.config.js中添加:
javascript复制module.exports = {
chainWebpack: config => {
config.module
.rule('bpmn')
.test(/\.bpmn$/)
.use('raw-loader')
.loader('raw-loader')
.end()
}
}
7.3 动态路由加载失败
若依的动态路由机制需要特殊处理Flowable路由:
javascript复制// 在permission.js中增加白名单
const whiteList = [
'/flowable/modeler',
'/flowable/task',
// ...其他Flowable相关路由
]
8. 扩展开发建议
8.1 与若依消息中心集成
改造任务分配通知:
java复制public class RuoYiTaskEventListener implements TaskEventListener {
@Autowired
private ISysNoticeService noticeService;
@Override
public void onEvent(FlowableEvent event) {
if (event instanceof FlowableEntityEvent) {
TaskEntity task = (TaskEntity) ((FlowableEntityEvent) event).getEntity();
SysNotice notice = new SysNotice();
notice.setNoticeTitle("待办任务提醒");
notice.setNoticeType("2");
notice.setNoticeContent(task.getName());
notice.setStatus("0");
notice.setCreateBy("system");
noticeService.insertNotice(notice);
}
}
}
8.2 移动端适配方案
使用Flowable REST API封装移动端接口:
java复制@RestController
@RequestMapping("/mobile/flowable")
public class MobileFlowableController {
@GetMapping("/todo/count")
public AjaxResult getTodoCount(@RequestParam String userId) {
long count = taskService.createTaskQuery()
.taskAssignee(userId)
.count();
return AjaxResult.success(count);
}
// 其他移动端专用接口...
}
在集成过程中发现,若依的权限拦截器会默认拦截Flowable的REST API,需要在ShiroConfig中添加排除路径:
java复制filterChainDefinitionMap.put("/flowable-rest/**", "anon");
