一套审批流要写多少代码:13 个框架的接入 diff 我数了一遍,最少 422 行,最多 1798 行

作者:mldong日期:2026/9/20

问"加一套审批流要写多少代码",很少有人能给出可信答案。原因不是大家懒得算,而是这句问话里其实混着三件事:给现有项目接一个现成引擎、从零做一套带审批的后台、自己造一个引擎。这三档的工期差两个数量级,却被同一句话压在一起。

这次我把第一档的数拿实了:jeeflow 已经在 13 个框架上各接入过一次,每一层的 diff 都还在 git 里。我把它们一个一个数了一遍,包括我自己数错的两次。

一、先交代我数错的两次

第一轮,我用集成壳里留着的 master 引用取合并基线。结果 NestJS 报出 7,077 行——那里面大半是框架自己这两个月的演进,跟"接入审批流"没有一行关系。基准必须从隔壁基础仓重新 fetch 之后再取。

第二轮,基准对了,git diff --shortstat 吐出来 14,762 行,比第二名整整多一个数量级。我盯着这个数看了半分钟才反应过来,去翻文件列表:

1package-lock.json                                    14022 +
2src/modules/wf/core/wf-message.listener.ts              340 +
3src/modules/wf/controller/wf.controller.ts               21 +-
4

14,022 行是一个锁文件,占掉 95%。它确实是这次提交写进去的,但没有任何人会在排期时把它算成"审批模块的开发量"。

第三轮才是下面用的口径:只统计新增和修改的文件(删除单独记一笔,不然"清掉旧引擎"会被记进接入成本),再剥掉锁文件、dist/ 这类产物和测试代码。

按这个口径,NestJS 那 14,762 行缩成 422 行,7 个源文件,最大的一个是 340 行的消息监听。虚报了 35 倍。

口径不先立住,后面所有数字都是噪音。所以这张文里每一个数,你都可以在任意一个集成仓里 git fetch 对应框架仓库、git diff --numstat <merge-base> HEAD 复现一遍。

二、13 个框架,422 到 1,798 行

框架实现代码消息监听HTTP 转发SPI 对接装配与工厂字典与元数据
NestJS422340190*630
C# ASP.NET Core7022069915923103
Django735207500*266202
FastAPI750276520*4148
Flask793276490*44127
Hertz1,01329922210036513
Gin1,01429922410036513
Java boot31,01831716239511826
GoFrame1,0712982421033850
Java boot21,07831716444113026
Java boot41,14531716244113095
Laravel1,3712611594213490
Rust Salvo1,7987521352646340

中位数 1,014 行,最厚与最薄差 4.3 倍。

换成原始 git diff(把测试和构建元数据算回来),同一批壳落在 702 到 1,852 行:最薄还是 C# 的 12 个文件,最厚还是 Salvo 的 21 个文件。

* 的几栏是分类假象,不是没写。Python 三栈和 NestJS 把用户、组织架构这些对接逻辑直接写进了 jeeflow_factory.py 这类装配文件里,按文件名归类就成了 0。它们确实在,只是没单独成文件——这本身就是个信息:文件怎么切,由语言生态的习惯决定;要写多少逻辑,由引擎留给你填的口子决定。

三、这一千行到底是什么

把最厚的 Java boot4 和最薄的 C# 逐文件摊开对照,会发现两边其实是同一套东西,只是切法不同。

boot4 的 1,145 行里:一个 WfMessageProcessEventListener 317 行负责听引擎事件写站内信;一个 WfFlowController 162 行把 40 多个 action 全转出去;WfProcessDictService 122 行和 MldongMetaProvider 112 行给流程设计器供字典与表单元数据;三个 provider 共 207 行回答"当前用户是谁、他部门领导是谁、怎么按名字搜人";两个 config 130 行把引擎实例挂进 Spring 容器;剩下 95 行是 5 个 pom.xml 的模块接线。

C# 的 702 行做的是同一件事:监听 206、四个 SPI 合并成一个 159 行的 WfProviders.cs、引擎服务 111、Minimal API 转发 99、字典 60、DI 注册 19。

比 boot4 少的 443 行,省的不是逻辑,是文件。 Java 要一个类一个文件、要有包名、要有独立的配置类和注册器;C# 一个文件塞四个类就行。

四、最厚的那块不是流程,是发消息和找人

把 13 个壳按职责堆起来看,绿色那段(消息监听)在每一行里都占着一大块:206 到 752 行,多数栈落在 28%~42%,NestJS 高到 81%、Laravel 低到 19%。

引擎本体那一块呢?0 行。因为流程流转的代码根本不在你仓库里。

原因很直接:引擎故意不发消息。 它的输入是"谁在哪个节点做了什么",输出是"下一个该谁做",不需要知道你们公司用站内信、企业微信还是钉钉机器人,也不该知道。所以这一层是空的,等你填。

而这一层从来填不便宜。任务创建要给办理人发一条,办理完成要给发起人回一条,抄送要按 N 个人各发一条,驳回要换文案,超时要补提醒,然后还得管已读未读、跳转链接、消息分类。写少了漏提醒被业务方追着问,写多了同一件事轰炸三遍。Salvo 那 752 行就是这么来的。

"找人"是同一个道理。boot 系和 Laravel 那 400 行 SPI 全花在这上面——引擎只肯接受一个结果:"这个节点该谁办"。至于这个人是不是副职、他领导出差了要不要往上跳一级,是你的组织树说了算。

上面那一千多行换到界面上,就是这张图:一个 Go 后端,六条待办排在那儿,会签、一票否决、按比例通过、报销各占一行,点「办理」进去填表单写意见。这张截图来自公开演示站 jeeflow-demo.mldong.com/?lang=go,右上角切到 Java、Python、Node、PHP、Rust、MoonBit、C# 任何一格,左边这一列待办和中间这个办理抽屉都不用改一行代码——因为那 13 个壳里的转发层,最终都收敛到同一个门面入口。

五、第二笔账:换成引擎之后少了 11,226 行

boot2 和 boot3 这两个壳特殊:它们本来自带一套内置工作流模块,切到 jeeflow 做的是减法

1新增 9 个、修改 3       +1,078 
2删除 180 个文件          -11,226 
3

git diff --shortstat 报 -11,236,多出的 10 行来自修改文件里的删除,按被删文件累加是 11,226。)

删掉的东西值得逐类看一眼,因为它说明了"自研一套审批流"的真实成本在哪:

删掉的类别文件行数
Service 层193,494
引擎内核与拦截271,732
模型与解析器271,233
实体与枚举191,034
VO 与 DTO311,006
Controller8945
Mapper 与 XML18649
其它(工具、调度)22580
处理人解析器9553

引擎本体只占 3,518 行(31%),剩下 7,708 行(69%)全是围着它写的脚手架——Service、Mapper、VO、DTO、实体、枚举。

这才是"自研很贵"的准确说法。贵的不是那台状态机,是它周围必须长出来的一整圈数据访问和接口皮。而这圈东西,boot2 和 boot3 当年是各自维护一份的(boot3 那笔删除是 -11,265 行),同一套语义写两遍,还得保证行为一致、两边一起修 bug。

六、第三档:自己造一台引擎要多久

我把 jeeflow-csharp 的提交时间线拉出来当尺子。那是我最省的一次,因为前面已经有七种语言垫着:

109-06 01:07  M0  仓骨架 + 45  action 清单 + 5 个技术验证
209-06 01:33  M1  引擎核心 + 内存仓储,100 个用例绿
309-06 02:00  M2  MySQL 仓储 + 事务模板,真库 120 用例绿
409-06 02:53  M3  持久化与门面 45  action,153 用例全绿
509-06 03:07  M4  demo 起服务 + 冒烟 20/20
609-06 03:28  M5  发版就绪收口
7

2 小时 21 分,一台新语言的工作流引擎。

然后从 09:31 到 12:01,又花掉两个半小时处理跟引擎一点关系都没有的事:发布流水线里 API Key 的输出名大小写写错导致 401、宿主机内存紧张 CoreCLR 起不来要压 GC 上限、老版本 Docker 的 seccomp 拦掉运行时系统调用、CI 机器上没有 Python 逼着把冒烟脚本改成纯 shell。

所以第三档的账要这么算:引擎本体两小时能立起来,把它变成别人能直接用的东西再搭半天进去,而这还只是一台引擎。 流程设计器、审批前端、移动端、八种语言之间的契约对齐和文档,都是另外的账。

七、三档工期,以及怎么自己动手验

你在哪一档量级真正花时间的是
① 已有项目接现成引擎千行级,一到两天消息监听和组织架构对接,不是流程
② 从零要一套带审批的后台一周级权限、组织、表单、附件这些引擎之外的东西
③ 自己造引擎月级起,且要一直养八种语义对齐、跨版本回归、别人提的 issue

第一档最实在的验证方式不是读我这篇文章,是在你自己的机器上跑一遍。十三套后端都配了一键部署脚本,挑你熟的栈,一条命令起一个带完整审批流的环境:

1curl -fsSL https://www.mldong.com/deploy/mldong-boot4-jeeflow/deploy.sh | bash
2

Go 系换 mldong-goframe-jeeflow / mldong-gin-jeeflow / mldong-hertz-jeeflow,Python 系 mldong-fastapi-jeeflow / mldong-flask-jeeflow / mldong-django-jeeflow,其余 mldong-nestjs-jeeflowmldong-laravel-jeeflowmldong-salvo-jeeflowmldong-csharp-jeeflowmldong-boot2-jeeflowmldong-boot3-jeeflow 同名同规则。起来之后登录进去,流程设计、发起、待办、审批、抄送、统计全都在——就是第四节那张图的那一屏。

参考资料


一套审批流要写多少代码:13 个框架的接入 diff 我数了一遍,最少 422 行,最多 1798 行》 是转载文章,点击查看原文


相关推荐


我做了一个 Chrome 扩展,把 YouTube 播放列表批量变成 AI 可读的本地 Markdown
嘉琪coder2026/9/12

现在的 AI 已经很擅长总结、分类和问答,但在真正使用它整理知识时,我经常卡在更前面的一步: 怎样把散落在视频里的内容,变成 AI 可以稳定读取的资料? 我平时会在 YouTube 上看课程、访谈和播客。看完时觉得收获很多,过一段时间想找其中一个观点,却只能重新打开视频、拖动进度条。 如果是一套几十集的课程或一个持续更新的频道,逐个打开视频、复制字幕、创建文件,光是准备资料就足以让人放弃。 所以我做了 Transcriptly。 它是一款免费、开源的 Chrome 扩展,可以把 YouTube


流程图上没画人,单子却走对了:工作流引擎内置处理人解析器
mldong2026/9/4

一、流程图上没画人,单子却走对了 用流程设计器画一张最简单的请假审批流:开始 → 部门领导审批 → 结束,三个节点两条线。 画完之后你检查一遍这张图:节点上没有任何人的名字。没有"张三",没有"李四",连"审批人"三个字都是你自己写的节点显示名。从图上看,这张流程不知道该把单子交给谁。 然后你发起了一笔请假。几秒后,你部门领导的待办列表里,多了这条单子。 它怎么知道该去找领导的? 你可能会说:节点名不是写着"部门领导审批"吗——但节点显示名只是个标签,引擎不会拿它去组织架构里搜人;就算你把节点改


Next.js 全栈笔记系统实战:RSC 组件架构、Redis 数据层与规范驱动开发
浮生望2026/8/27

摘要 以Next.js笔记系统为例,深入RSC异步组件直接查询Redis、Server/Client组件边界、BEM命名规范、路径别名及组件驱动开发流程,展示从需求分析到技术方案到组件拆分的全栈落地路径。 一个笔记系统的需求并不复杂:左侧笔记列表,右侧 Markdown 编辑预览,支持 CRUD 和搜索。但用 Next.js 全栈实现它,涉及的技术决策覆盖了 RSC 组件边界、数据层选型、组件拆分策略和项目结构规范——这些正是 Next.js 工程化的核心议题。 技术选型:为什么是 Redis


JavaScript实战技巧总结
阿橙的百宝箱2026/8/19

JavaScript实战技巧总结* 引言 JavaScript作为现代Web开发的基石,其灵活性和强大的功能使其成为开发者必备的技能之一。然而,随着ECMAScript标准的不断演进和前端生态的日益复杂,如何高效、优雅地编写JavaScript代码成为开发者面临的重要课题。本文将从实际出发,总结一系列经过实战检验的JavaScript技巧,涵盖性能优化、代码简洁性、异步处理、模块化等多个关键领域,帮助开发者提升代码质量和开发效率。 一、性能优化技巧 1. 减少DOM操作 DOM操作是Java


篇2-bitsandbytes-具体观-算法与实现剖析
happyprince2026/8/4

三部曲之二 · 看懂:潜入水下的技术细节 本篇承接整体观建立的认知地图,潜入水下逐一审视 bitsandbytes 的核心技术实现——LLM.int8()、QLoRA/NF4、8-bit 优化器,以及支撑它们的极致工程。每个技术点遵循"原理(论文)→ 代码 → 例子"三段式剖析,让读者真正"看懂"代码背后的学术根源与工程取舍。 一、总述:三大算法支柱 + 一条工程脊梁 bitsandbytes 的技术内核可拆成三大算法支柱 + 一条工程脊梁。三大算法支柱是:① LLM.int


把随身WiFi改成网盘聚合器:中兴F50挂载本地存储+夸克网盘实战
羑悻的小杀马特.2026/7/27

文章目录 前言1 什么是OpenList?2 中兴F50上安装OpenList服务3 挂载本地存储和网盘存储3.1 挂载本地F50自带的20G存储3.2 挂载夸克网盘 4 穿透OpenList以支持公网访问4.1 如何用?4.2 在F50上安装4.3 配置OpenList的http隧道 5 固定二级子域名(升级任意套餐)总结 前言 中兴F50放在包里时,很多人只把它当作一台提供热点的随身WiFi。需要传文件、打开网盘资料或临时分享内容时,仍然要依赖手机、电脑和多个客户端


【数据库】CRUD-- 增删改
哦虎!2026/7/19

第二章 增删改 文章目录 第二章 增删改前言一、命名二、增1. 新增2. 指定列插入3. 多行插入4. 插入查询结果5. 查询的指定列插入6.注意 三、改1.修改数据2.组内修改 四、删除1. 删除数据2.drop和delete的区别 五、截断表总结 前言 本期主要讲增 删 改, 会稍微使用到查 , 也就是select , 遇到会稍做讲解 , 详细讲解会放到下期~ 一、命名 创建表时,命名有两种 驼峰命名(studentName)蛇形命名(stud


C++内存管理
孬甭_2026/7/11

目录 1 · C / C++ 内存分布 2 · C++内存管理方式 2 - 1 · new / delete 操作内置类型 2 - 2 · new / delete 操作自定义类型 3 · operator new 和 operator delete 函数 4 · new 和 delete 的实现原理 4 - 1 · 内置类型 4 - 2 · 自定义类型 5 · 定位new 6 · new / delete 与 malloc / free 的区别 1 · C / C


EventBus → SharedFlow
plainGeek2026/7/3

EventBus → SharedFlow 老写法(Java + EventBus) // 事件定义 public class LoginEvent { private long userId; public LoginEvent(long userId) { this.userId = userId; } public long getUserId() { return userId; } } // 发送 EventBus.getDefault().post(new


【Linux基础】初始Linux
键盘敲碎了雾霭2026/6/25

🎬 博主名称:键盘敲碎了雾霭 🔥 个人专栏: 《C语言》《数据结构》 《C++》 《Matlab》 《Python》 《Linux》 ⛺️指尖敲代码,雾霭皆可破 文章目录 一、Linux发展史1.1 UNIX发展历史1.2 Linux发展历史 二、开源三、应用现状四、发行版本五、os概念六、使用XShell远程登录Linux6.1 会话方式登录6.2 命令行登录6.3 多用户使用 文章结语 一、Linux发展史 要说Linux,还

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读