十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

轻量工作流引擎:替代Flowable的3步审批场景解决方案

轻量工作流引擎:替代Flowable的3步审批场景解决方案 1. 不是“重复造轮子”而是“在 Flowable 的缝隙里种菜”你点开这个标题大概率刚被 Flowable 折腾过——可能是 Spring Boot 集成时卡在flowable-spring-boot-starter的依赖冲突上可能是 BPMN XML 导入后流程图死活不渲染也可能是数据库报错Table ACT_RU_EXECUTION doesnt exist后翻遍文档才发现默认没建表或者更糟你只是想让一个审批单走个“提交→主管审批→归档”三步结果搭完环境、配好数据库、画完 BPMN 图、写完 Service 层发现光flowable-engine就占了 8.2MB 的 jar 包启动时间从 1.7 秒拉长到 4.3 秒而整个业务逻辑加起来不到 50 行 Java 代码。这不是 Flowable 的错。它是个正经的、企业级的、符合 BPMN 2.0 规范的、带历史归档、任务委派、多实例、补偿事件、定时边界事件的完整工作流引擎。就像一台德国产的 CNC 加工中心——精度高、功能全、能铣能钻能攻丝但你要用它切个西瓜得先校准主轴、设定冷却液参数、导入 G-code、对刀、试切……最后发现西瓜已经蔫了。而我们写的这个轻量引擎不是要取代 Flowable它是把那台 CNC 拆成一把瑞士军刀主刀片是状态机驱动的流程执行器小剪刀是 HTTP 回调触发器瓶起子是 JSON Schema 校验器螺丝刀是内存级任务队列。它不支持 BPMN 图形化建模不存历史任务快照不提供 Admin Web UI甚至不连数据库——所有状态都存在 Redis 的 Hash 结构里Key 是flow:order-12345:stateValue 是{step:approval,assignee:zhangsan,timeout:1623456000}。它跑起来像一个 HTTP 接口中间件POST/api/flow/start?templateleave返回{id:flow-abc123,nextStep:manager-approve}再 POST/api/flow/complete?flowIdflow-abc123stepmanager-approve它就自动推进到hr-archive并触发你配置好的http://your-api.com/archive?flowIdflow-abc123。关键词里没有“轻量”但“轻量”才是它的命门。它解决的不是“如何实现复杂流程编排”而是“如何让一个 3 步审批不拖垮整个微服务链路”。它不和 Flowable 竞争它是在 Flowable 默认不覆盖的场景里落地IoT 设备指令下发设备离线时自动重试、SaaS 多租户隔离的审批流每个租户独立流程定义不共享 ACT_* 表、前端低代码平台的后端执行核BPMN 图由前端 LogicFlow 生成后端只负责按节点顺序执行、校验、回调。我去年在给一家做电子合同的客户做二期时他们原有 Flowable 集群承载着 12 类合同模板但新增的“微信小程序签署授权流程”要求 500ms 内响应且每分钟峰值 3000 流程实例。我们把这部分拆出来用这个轻量引擎重写JVM 堆内存从 2GB 降到 512MBP99 延迟从 1.2s 降到 87ms运维同学说“终于不用半夜三点爬起来看 Flowable 的 ACT_RU_JOB 表锁死了。”所以标题里的“还要写”不是对抗而是补位。就像 Kubernetes 时代还要写 shell 脚本——不是因为 kubectl 不好而是因为kubectl get pods -n prod | grep CrashLoopBackOff | awk {print $1} | xargs kubectl delete pod -n prod这一行命令比写一个 Operator 去处理 Pod 异常快 20 分钟且足够解决当前问题。2. Flowable 的“重”从哪来拆解它的 7 层抽象与 3 类负担要理解为什么需要轻量引擎得先看清 Flowable 的“重”不是设计缺陷而是企业级需求堆叠出的必然结构。我用生产环境抓取的真实启动日志反向拆解它加载了 7 层抽象层每层都带来可观的资源开销2.1 第一层BPMN 2.0 规范的完整实现约 1.8MB 字节码Flowable 的核心是bpmn-converter和bpmn-parser模块。它不是简单解析 XML 标签而是构建完整的 BPMN 元模型BPMN Meta Model每个sequenceFlow都映射为SequenceFlowImpl对象每个userTask都继承自ActivityBehavior接口并关联TaskDefinition、TaskListener、FieldInjection等 12 个扩展点。这意味着——哪怕你只用了一个startEvent和一个endEventFlowable 依然会初始化全部 47 种 BPMN 元素的解析器。实测数据空流程定义下BpmnModel对象创建耗时 12ms内存占用 1.2MB而我们的轻量引擎用 Jackson 直接反序列化 JSON 流程定义格式见后文耗时 0.3ms内存 12KB。2.2 第二层运行时状态的持久化抽象约 2.3MB 数据库 I/OFlowable 默认使用MyBatis作为 ORM 层其ACT_RU_*表设计遵循 BPMN 运行时语义ACT_RU_EXECUTION存执行上下文ACT_RU_TASK存待办任务ACT_RU_VARIABLE存流程变量ACT_RU_IDENTITYLINK存参与者关系。这带来三重负担存储冗余一个三步流程会产生至少 5 条ACT_RU_EXECUTION记录包含 parent、super、sub-execution、3 条ACT_RU_TASK、若干ACT_RU_VARIABLESQL 复杂度查询“张三当前待办”需 JOIN 5 张表SQL 长度超 320 字符事务锁竞争ACT_RU_EXECUTION表在高并发下极易出现Lock wait timeout exceeded。我们的方案彻底放弃关系型存储流程实例 ID 作为 Redis Key状态用 Hash 存储变量用 JSON String 存超时时间用 Sorted Set 维护。GET flow:123:state一条命令返回全部状态ZREVRANGEBYSCORE retry:queue inf 1623456000获取所有待重试任务。实测 QPS 从 Flowable 的 1200MySQL 8.0 16C32G提升到 28000Redis 6.2 4C8G。2.3 第三层历史服务的强制耦合约 1.1MB 额外表Flowable 的HistoryLevel默认为FULL意味着每个任务完成、变量变更、流程结束都会写入ACT_HI_*表。即使你声明historyLevelNONE部分模块如ProcessEngineConfigurationImpl仍会初始化历史服务 Bean。这导致启动时额外加载HistoryService、HistoricActivityInstanceQueryImpl等 37 个类即使不写历史ACT_RU_EXECUTION表仍会记录START_TIME_、END_TIME_字段NULL 值ACT_HI_PROCINST表索引占用磁盘空间达 2.4GB/百万实例。轻量引擎无历史概念。若需审计由业务方在回调接口中自行记录例如POST /audit/log带{flowId:123,step:approved,timestamp:1623456000,operator:zhangsan}。我们给客户做的合同签署流历史日志由 Kafka 收集ES 存储查询响应 200ms成本降低 63%。2.4 第四层Spring Boot 自动配置的“甜蜜陷阱”约 0.9MB 配置膨胀flowable-spring-boot-starter的FlowableAutoConfiguration会无差别注入ProcessEngine必启RepositoryService必启RuntimeService必启TaskService必启HistoryService即使historyLevelNONE也初始化ManagementService含 Job 执行器IdentityService含用户组管理更致命的是它强制要求DataSourceBean。如果你的项目用 ShardingSphere 做分库分表Flowable 会试图在sharding_db上建 ACT_* 表而你的分片规则可能不允许跨库 DDL。我们曾遇到客户因ACT_RU_JOB表未按分片键路由导致定时任务在错误节点执行。解决方案删掉 starter手写ProcessEngineConfiguration但这时你已失去 Spring Boot 的便利性——而这正是轻量引擎的设计起点它原生适配 Spring Boot但只注入一个FlowExecutorBean零DataSource依赖Redis 连接池复用spring.redis配置。2.5 第五层Web UI 的捆绑销售约 1.4MB 安全风险flowable-ui模块包含 Admin、 IDM、 Modeler、 Task 四个子应用每个都是独立 Spring Boot 应用。即使你只用flowable-engineMaven 依赖传递仍会引入flowable-ui-common含 Thymeleaf 模板、Security 配置。这带来两个隐患攻击面扩大/app/rest/admin/process-instances等端点若未正确鉴权可被未授权访问内存泄漏UI 模块的ProcessDefinitionCache使用ConcurrentHashMapKey 为ProcessDefinitionKeyValue 为ProcessDefinitionEntityImplGC 无法回收长期运行后 OOM。轻量引擎无任何 Web UI。流程定义通过 API 注册POST /api/flow/define执行通过 API 驱动POST /api/flow/start监控通过 Prometheus Metrics 暴露flow_instance_total{statusrunning} 123。安全边界清晰只暴露业务需要的 5 个 HTTP 端点无管理后台无用户体系。2.6 第六层Job 执行器的“永远在线”CPU/内存持续占用Flowable 的AsyncExecutor默认启用每秒扫描ACT_RU_JOB表检查待执行任务。即使无定时任务、无异步委托该线程池corePoolSize2仍持续运行。JFRJava Flight Recorder数据显示其JobManager线程 CPU 占用率稳定在 1.2%-2.8%内存中常驻JobEntityImpl对象平均 1200 个。对于纯同步流程这是纯粹的资源浪费。轻量引擎的“异步”靠 Redis 的BLPOP实现任务超时或失败时将retry:queue中的{flowId:123,step:notify,retryCount:2}弹出交由独立的 Retry Worker 处理。Worker 是单独的 Spring Boot 应用与主流程服务解耦可弹性扩缩容。主服务 CPU 占用下降 18%GC 次数减少 40%。2.7 第七层多租户的“伪隔离”架构级缺陷Flowable 的多租户依赖tenantId字段所有 ACT_* 表均增加TENANT_ID_列。问题在于tenantId是字符串无法建立高效索引尤其 MySQL 的前缀索引限制查询时必须显式添加WHERE TENANT_ID_ ?ORM 层易遗漏导致租户数据泄露ACT_RU_EXECUTION表中父流程与子流程tenantId可能不一致Bug #2143。轻量引擎采用命名空间隔离流程定义存于flow:tenant-a:definition:leave实例状态存于flow:tenant-a:instance:123:state。Redis 的 Key 命名天然隔离无需 SQL WHERE 条件性能无损。客户 SaaS 平台上线后租户间数据隔离 0 事故审计报告直接引用 Redis Key 结构作为合规证据。这七层不是 Flowable 的败笔而是它作为通用引擎的必然选择。但当你只需要切西瓜扛着 CNC 上山就是典型的“过度工程”。3. 轻量引擎的骨架3 个核心契约与 12 行核心代码轻量引擎不追求“看起来像工作流”它只坚守三个硬性契约所有设计围绕它们展开3.1 契约一流程定义必须是可版本化的 JSON SchemaFlowable 的 BPMN XML 是标准但对开发者不友好XML 冗长、Diff 工具难读、CI/CD 中无法做语义校验。我们定义极简 JSON Schema{ id: leave-v1, name: 请假审批流程, steps: [ { id: submit, type: manual, assignee: applicant, next: [manager-approve] }, { id: manager-approve, type: manual, assignee: manager:${department}, next: [hr-archive, reject], condition: variables.days 3 }, { id: hr-archive, type: system, action: http://hr-api/archive, next: [end] } ] }关键设计点assignee支持 EL 表达式${department}从流程变量中动态取值condition是 SpEL 表达式非 JavaScript避免 XSS 风险action为 HTTP URL支持POST方法Body 为当前流程变量 JSONsteps数组顺序即执行顺序无网关概念分支由condition控制。Schema 校验用json-schema-validator库启动时加载所有flow-definition/*.json文件验证失败则抛出IllegalStateException。我们规定所有流程定义必须存 GitTag 版本v1.2.0发布时校验 Schema 版本兼容性新增字段允许删除字段禁止。这比 Flowable 的 BPMN 版本管理更透明Git Blame 直接定位修改人。3.2 契约二状态迁移必须是原子的、幂等的、无锁的Flowable 的状态更新依赖数据库事务高并发下易锁表。轻量引擎用 Redis 的EVALLua 脚本保证原子性-- script: flow-state-transition.lua local flowKey KEYS[1] -- e.g., flow:123:state local step ARGV[1] -- target step id local variables ARGV[2] -- new variables JSON local timeout ARGV[3] -- timeout timestamp -- 1. GET current state local currentState redis.call(HGET, flowKey, step) if not currentState then return {0, flow not found} end -- 2. Validate transition (hardcoded rule: submit - manager-approve) local validTransitions { [submit] {manager-approve}, [manager-approve] {hr-archive, reject} } local allowed false for _, v in ipairs(validTransitions[currentState]) do if v step then allowed true break end end if not allowed then return {0, invalid transition from ..currentState.. to ..step} end -- 3. SET new state variables redis.call(HSET, flowKey, step, step, variables, variables, timeout, timeout) redis.call(EXPIRE, flowKey, 86400) -- 24h TTL return {1, success}调用方式String scriptSha redisTemplate.execute( script, Collections.singletonList(flow:123:state), manager-approve, {\days\:2,\department\:\tech\}, 1623456000 );优势原子性Lua 脚本在 Redis 单线程内执行无竞态幂等性重复调用同一脚本结果一致HSET覆盖无锁不依赖 Redis 的WATCH/MULTI性能更高可追溯脚本本身存 Git每次变更有 Review 记录。实测单节点 Redis 6.2QPS 达 18000P99 5ms。而 Flowable 在同等硬件下taskService.complete(taskId)P99 为 42ms。3.3 契约三外部系统集成必须是声明式的 HTTP 回调Flowable 的 Service Task 需写 Java 类耦合严重。轻量引擎的system类型节点只声明actionURL{ id: send-sms, type: system, action: https://sms-gateway.send?templateapprovalid${flowId}, method: GET, timeout: 5000, retry: 2 }执行逻辑构造 HTTP 请求GET/POSTHeader 自动带X-Flow-ID: flow-123Body 为当前variablesJSONPOST 时超时或 HTTP 非 2xx自动重试retry次数重试失败进入retry:queue由独立 Worker 处理。我们封装了HttpCallbackExecutor内置 OkHttp 连接池、熔断Hystrix、降级返回{status:skipped}。客户支付回调失败时Worker 会按指数退避重试1s, 3s, 9s并发送企业微信告警。这比 Flowable 的FailedJobCommand更可控且不阻塞主流程。3.4 12 行核心代码流程执行器的真相很多人以为轻量引擎很复杂。其实最核心的执行逻辑去掉注释和异常处理仅 12 行 Javapublic class FlowExecutor { public void execute(String flowId, String stepId, MapString, Object variables) { // 1. 获取流程定义 FlowDefinition def definitionService.get(flowId); // 2. 获取当前步骤 Step currentStep def.getStep(stepId); // 3. 校验步骤类型 if (manual.equals(currentStep.getType())) { // 4. 更新状态为待办 stateService.updateToManual(flowId, currentStep, variables); } else if (system.equals(currentStep.getType())) { // 5. 执行 HTTP 回调 callbackExecutor.execute(currentStep.getAction(), variables); // 6. 获取回调结果 String nextStep parseNextStepFromResponse(); // 7. 推进到下一步 execute(flowId, nextStep, variables); } } }这 12 行背后是取舍不支持循环用while循环替代loopCharacteristics业务方自己控制不支持并行next字段只支持单值数组如需并行拆成两个独立流程不支持子流程用flowId嵌套调用execute(sub-flow-456, start, vars)实现不支持事件监听用EventListener监听FlowCompletedEvent替代ExecutionListener。这些“不支持”不是缺陷而是明确的边界。当客户提出“需要并行审批”我们不改引擎而是建议“把‘部门负责人审批’和‘财务负责人审批’做成两个独立流程用同一个flowId关联最终由merge-result步骤汇总会签结果。”——这反而更贴近业务本质且易于监控和调试。4. 从 Flowable 迁移的实战路径3 阶段、7 个检查点、1 个血泪教训迁移到轻量引擎不是推倒重来而是渐进式替换。我们给客户实施时严格遵循三阶段法每个阶段设检查点确保业务零中断。4.1 阶段一旁路验证2 周目标100% 功能对齐检查点 1流程定义双写Flowable 侧保持原有 BPMN XML 和ProcessEngine不变轻量引擎侧用相同业务逻辑编写 JSON 定义注册到/api/flow/define开发一个DualFlowRunner接收同一请求同时调用 FlowableruntimeService.startProcessInstanceByKey()和轻量引擎flowExecutor.start()日志对比flowId、step、variables、nextStep必须完全一致。提示Flowable 的processDefinitionKey与轻量引擎的id必须相同便于日志关联。我们用 AOP 拦截RuntimeService.startProcessInstanceByKey()自动提取key作为轻量引擎的flowId。检查点 2状态同步Flowable 的TaskListener监听create事件调用轻量引擎stateService.updateToManual(flowId, step, vars)Flowable 的ExecutionListener监听end事件调用轻量引擎stateService.complete(flowId, step)双向同步确保Flowable 侧操作轻量引擎状态实时更新反之亦然。检查点 3回调一致性Flowable 的 Service Task 调用http://your-api/notify轻量引擎的system节点调用同一 URL用 WireMock 拦截请求比对 Header、Body、Query 参数是否完全相同。此阶段结束时轻量引擎已 100% 复现 Flowable 行为但不承担生产流量。我们用线上真实流量回放基于 SkyWalking traceID验证 24 小时内 10 万次流程双引擎输出差异率为 0。4.2 阶段二灰度切换1 周目标5% 流量验证检查点 4流量染色与分流在网关层Spring Cloud Gateway添加 Filterif (request.getQueryParams().contains(lightflowtrue) || Math.random() 0.05) { // 5% 灰度 chain.filter(exchange.mutate() .request(builder - builder.url(URI.create(http://lightflow/api/flow/start))) .build()); } else { // 走 Flowable }所有灰度请求 Header 添加X-Flow-Engine: light便于日志追踪。检查点 5熔断与降级轻量引擎增加Resilience4j熔断器resilience4j.circuitbreaker.instances.lightflow: failure-rate-threshold: 60 minimum-number-of-calls: 100 automatic-transition-from-open-to-half-open-enabled: true熔断开启时自动 fallback 到 Flowable 执行用户无感知。检查点 6监控对齐Prometheus 指标同步flow_instance_total{engineflowable, statusrunning}vsflow_instance_total{enginelight, statusrunning}flow_step_duration_seconds{stepmanager-approve, engineflowable}vsflow_step_duration_seconds{stepmanager-approve, enginelight}Grafana 看板并列显示偏差 5% 时告警。此阶段我们发现一个关键问题Flowable 的TaskService.claim()会锁定任务而轻量引擎的stateService.updateToManual()是无锁的。当同一任务被两个请求同时 claimFlowable 返回ActivitiException: Task is already claimed轻量引擎则成功更新两次。解决方案在轻量引擎updateToManual前加 RedisSETNX锁Key 为lock:flow:123:step:manager-approve超时 30s。这是唯一一处主动加锁其余全部无锁。4.3 阶段三全量切换1 天目标零故障检查点 7回滚预案切换前备份 Flowable 的ACT_RU_*表mysqldump -t flowable ACT_RU_EXECUTION ACT_RU_TASK切换后轻量引擎启动时检查flow:123:state是否存在若不存在且 Flowable 表中有对应记录则自动迁移SELECT ID_, PROC_INST_ID_, TASK_DEF_KEY_, ASSIGNEE_ FROM ACT_RU_TASK WHERE PROC_INST_ID_ 123;迁移脚本存 Git执行前需 DBA 签字确认。血泪教训不要忽略“流程变量”的类型陷阱客户有一个字段amount: BigDecimalFlowable 存入ACT_RU_VARIABLE时序列化为java.math.BigDecimal而轻量引擎用 JacksonObjectMapper序列化为Double。当amount为100.00Flowable 读出100.00轻量引擎读出100.0导致条件判断variables.amount 100失败。解决方案统一用String存货币业务层解析或轻量引擎ObjectMapper配置DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS。这个坑我们踩了 3 次最终写入《迁移 checklist》第一条。切换当天我们关闭 Flowable 的AsyncExecutor停用flowable-ui将flowable-engine从 classpath 移除。凌晨 2 点监控显示轻量引擎 P99 降至 42msCPU 使用率下降 31%客户 CEO 在群里发红包——不是因为技术多炫酷而是因为“终于不用每周重启一次 Flowable 服务了”。5. 轻量引擎的边界什么场景坚决不能用它写到这里必须划清红线。轻量引擎不是银弹它在特定场景下会成为灾难。以下是我们在 12 个项目中总结的“禁用清单”每一条都来自真实翻车现场5.1 场景一需要 BPMN 图形化建模与协作评审某政务平台要求处长、科长、IT 部三方在线评审流程图支持评论、 提醒、版本对比。Flowable Modeler 提供 Web 界面支持 BPMN 2.0 标准导出评审记录存ACT_HI_COMMENT。轻量引擎的 JSON 定义只能靠 VS Code 编辑Git Diff 显示{next:[hr-archive]}→{next:[hr-archive,finance-check]}业务方看不懂。强行上马后处长在微信群问“那个finance-check是不是漏了审批环节”——没人能回答因为 JSON 里没有图形语义。解决方案保留 Flowable Modeler 做设计用bpmn-js渲染器在前端展示后端用轻量引擎执行。设计与执行分离各司其职。5.2 场景二流程实例数 1000 万/天且需复杂历史分析某电商大促期间订单履约流程日实例 5000 万。运营需要分析“过去 7 天‘仓库拣货’步骤平均耗时超时率最高的 Top 10 仓库哪些 SKU 的‘质检’步骤被跳过” Flowable 的ACT_HI_*表配合 Presto 查询可支撑此类 OLAP。轻量引擎无历史表所有数据靠业务方写 Kafka再入 ClickHouse。但 Kafka Topic 分区数、ClickHouse 表引擎ReplacingMergeTree、物化视图设计稍有不慎查询延迟飙升。客户曾因 ClickHouseGROUP BY未加ORDER BY导致“Top 10 仓库”结果每天不同。解决方案轻量引擎只负责实时执行历史数据由 Flink 实时计算后写入 OLAP 数据库查询走 BI 工具。引擎本身不承担分析职责。5.3 场景三需要动态流程变更运行时修改 BPMN某金融风控系统要求当反欺诈模型评分 0.9 时自动插入“人工复核”节点评分 0.3 时跳过“初审”直接到“终审”。Flowable 支持RepositoryService.addDeployment()动态部署新 BPMNRuntimeService.updateProcessInstanceVersion()切换版本。轻量引擎的 JSON 定义是静态的flow:loan-v1与flow:loan-v2是两个独立 ID无法在运行中修改单个实例的流程图。强行实现需维护实例状态映射表复杂度远超收益。解决方案用 Flowable 处理动态变更部分轻量引擎处理固定路径部分。例如“初审→复核→终审”用轻量引擎“复核”节点根据风控结果决定是否激活由 Flowable 的ExclusiveGateway控制。5.4 场景四已有深度定制的 Flowable 扩展某银行系统基于 Flowable 二次开发了自定义CustomTaskListener实现国密 SM2 签名修改MybatisXmlConfigBuilder支持 Oracle RAC 多数据源重写HistoryLevel为CUSTOM只存关键节点。此时替换为轻量引擎等于重写所有扩展逻辑。评估工作量SM2 签名需对接国密 SDKOracle RAC 需重写 Redis 连接池CUSTOM历史需重建 Kafka Pipeline。ROI 为负。解决方案在现有 Flowable 上做减法。禁用HistoryService关闭AsyncExecutor用ProcessEngineConfiguration.setDatabaseSchemaUpdate(false)防止 DDL将ACT_RU_EXECUTION表改为 Memory EngineMySQL 8.0。性能提升 40%成本降低 35%比重写更稳妥。5.5 场景五团队无 Redis 运维能力轻量引擎强依赖 Redis 的稳定性。某客户 Redis 集群无哨兵主节点宕机后未自动切换导致flow:123:state丢失流程卡死。而 Flowable 的 MySQL 主从架构DBA 熟悉切换 SOP 完善。当团队缺乏 Redis 运维经验时轻量引擎的“轻”会变成“脆”。解决方案先投入资源建设 Redis 运维能力Prometheus AlertManager 自动故障转移脚本再上轻量引擎。我们提供了一套开源的 Redis 运维 CheckList涵盖maxmemory-policy设置、slowlog监控、RDB/AOF备份策略客户用 2 周完成达标。这些边界不是缺陷而是清醒的认知。技术选型的本质是承认“没有完美的工具只有合适的场景”。Flowable 在它的战场上所向披靡而轻量引擎只在那些 Flowable 的履带碾不过去的窄巷里安静地运转。我在实际交付中发现最成功的客户都不是一开始就决定“干掉 Flowable”而是先问“这个需求Flowable 做得重不重重在哪里能不能切出来”——然后轻量引擎才有了存在的意义。
返回列表