三栈实战复盘:同一套流水线的伸缩性
三个项目,一个流水线
三个真实项目先后上了这条流水线(流水线的全景见系列总览)。报销单影像自动化系统:Python 写的脚本群,Selenium 驱动浏览器、OCR 识别票据影像、Requests 调接口回写状态,没有一个传统意义上的「页面」。SAP 自动清账平台:Spring Boot 加 MyBatis 的后端,真正干活时还要通过 VBS 脚本操纵 SAP GUI 完成清账动作。质控往来风险探针:Spring MVC 加 Activiti 工作流加 Hibernate 的遗产系统,前端是 ExtJS 组件和 jqGrid 表格,典型的十几年前的企业应用画风。
三套系统的共同点只有两条:都在长期运行中积累了无人能完整说清的业务逻辑,都迫切需要文档资产。共同点就这么多,技术栈从脚本语言到企业级 Java 全覆盖。如果流水线只对其中一种好用,它就只是某个项目的私产;三种都能跑,它才配叫通用流水线。
flowchart LR
subgraph P1[报销单影像自动化]
A1[Python + Selenium + OCR]
end
subgraph P2[SAP 自动清账平台]
A2[Spring Boot + MyBatis + VBS + SAP GUI]
end
subgraph P3[质控往来风险探针]
A3[Spring MVC + Activiti + ExtJS + jqGrid]
end
A1 --> L[同一条流水线: 功能点提取 链路图 业务需求 归类合并索引]
A2 --> L
A3 --> L
L --> O[五类文档资产 + 数据字典]
案例一:报销单影像自动化
这套系统没有前端页面,功能入口全是脚本函数。目录监视主线程轮询下载目录,新文件到齐后分发处理:锁等待确认文件写完、解压压缩包、OCR 识别二维码和票据要素、调后端接口回写状态,必要时 psycopg2 直连数据库查补数据。
最难啃的是两处。一是功能入口的认定:按「找按钮」的思路颗粒无收,功能点提取的 Python 栈规则把主函数、__main__ 入口和主流程方法当入口,再配合「批处理步骤函数分别登记」的要求,把整条流水线拆成了链路图画得下、业务规则写得清的功能点。二是非典型输入:OCR 识别出的票号、金额也是输入参数,和表单字段平起平坐地进输入参数表,这在传统 Web 系统里没有对应物,业务需求生成的十类输入来源清单专门覆盖了这类情况。
这套系统跑完流水线,散落在几十个脚本里的业务逻辑第一次有了一份按功能组织、能查到每个接口调用和 SQL 的文档。
案例二:SAP 自动清账平台
Java 后端部分是流水线最熟悉的形态:Controller、Service、Mapper 接口、XML 里的 SQL,一路顺藤摸瓜到 PostgreSQL。真正的难点在链路的末端:清账动作最终由 VBS 脚本操纵 SAP GUI 完成,这一段既不在 Java 代码里,也不在任何 SQL 里。
链路图的 External 标注在这里发挥了作用:External: SAP GUI 节点标在链路终点,边框按确认与推测区分为实线或虚线,配套的 VBS 脚本片段作为外部调用代码提取进功能点。业务需求文档里相应出现「系统调用 SAP 完成清账,超时转人工处理」这类规则——业务规则来自链路证据,而不是凭空概括。
数据字典这一步顺带把 SAP 交互涉及的中间表梳理了出来。逆向完成后,新人和运维第一次能对着链路图说清楚:一次清账从页面点击到 SAP 回执,中间一共发生几件事。
案例三:质控往来风险探针
这是最老的一套:JSP 页面里嵌着 ExtJS 组件,表格用 jqGrid 渲染,业务流程跑在 Activiti 工作流引擎上,数据访问走 Hibernate。三个老部件各出一道难题。
ExtJS 的事件不写在标签里,而是 grid.on(...)、button.on(...) 的编程式绑定,功能点提取的入口特征表专门列了这类写法。jqGrid 的列定义天然是最好的输出字段基准:colModel 里每个字段的 label 就是业务展示名,比从 SQL 别名反推更直接,输出字段的三级优先级里给了它应有的位置。Activiti 工作流的链路表达最微妙:任务节点、网关、流转条件构成另一张图,流水线的处理是把关键审批节点作为链路上的业务规则来源,写进对应功能点的规则清单,而不是试图把两张图强行合一。
这套系统跑完后最直接的收益是审计支持:风险探针的每个探针规则、每条告警的判定逻辑,都能从功能点详情一路追到具体的 Hibernate 查询。
同与不同:一张对照表
三个项目跑下来,七个 skill 的复用情况高度规律:越靠近业务语义的步骤越通用,越靠近技术栈的步骤越需要定制。
| 步骤 | 报销影像(Python) | 清账平台(SpringBoot) | 风险探针(Legacy MVC) |
|---|---|---|---|
| 功能点提取 | 按脚本入口特征定制 | Vue 事件规则现成 | ExtJS 编程式事件规则定制 |
| 链路图 | Python 模板,无前端链 | Java+MyBatis 模板 | MVC+Hibernate 模板 |
| 业务需求 | 通用,零改动 | 通用,零改动 | 通用,零改动 |
| 归类/合并/索引 | 通用,零改动 | 通用,零改动 | 通用,零改动 |
| 数据字典 | 按方言生成注释脚本 | 按方言生成注释脚本 | 按方言生成注释脚本 |
| feature-dev | 按栈的底座说明 | 按栈的底座说明 | 按栈的底座说明 |
规律背后是设计意图:技术栈差异被尽量压缩在前两个 skill 里消化掉,从业务需求开始,所有产物都是技术中立的业务语言。所以定制的成本集中且一次性,而下游全部步骤——恰恰是占流水线大头的文档工程部分——完整复用。
External 标注的实战价值
三个项目里的异构依赖五花八门:SAP GUI、OSS 存储、OCR 服务、第三方接口。统一用灰色背景节点加 External: 前缀标注后,出现了一个没预料到的用法:把所有链路图放在一起看,外部依赖清单自动浮现——哪些功能依赖 SAP、哪些依赖第三方接口、哪些脚本直连了 OSS,一目了然。
这份「长出来的」外部依赖视图,做变更影响分析时极其好用:SAP 接口要升级,受影响的功能点沿着 External 节点反查链路即可,不用全库搜索代码。一个为画图清晰设计的标注规则,在实战里升格成了依赖治理工具。
伸缩性的三个来源
复盘下来,同一套流水线能覆盖三栈,靠的是三件事。识别前置:技术栈判断集中在第一步,结论写进 JSON 传递,下游不做重复判断。模板路由:每类栈一张链路地图,模型按图作业而不是自由发挥。业务中立:从第三步起强制业务语言,技术差异在产物层面消失。
三件事的共同名字叫分层:把「随技术栈变化的」和「随业务稳定的」切开,变化的部分做成可替换的模板,稳定的部分沉淀成通用规则。这套分层是怎么来的、改造过程中划了哪条参数与原则的分界线,见从专用到通用:Skill 的技术栈中立化改造。