资讯动态

代码化绘图实践:用Mermaid与CI实现图表版本化管理

发布时间:2026/10/21 19:40:39 来源:尧图企业网站定制
写代码画图这件事我一开始是拒绝的。用了那么多年鼠标拖拖拽拽不香吗直到某一天我需要给一个持续迭代的模块画架构图改了三次需求我重画了四遍而且文档里那张图和线上代码已经差了三个版本。也就是在那一刻我决定把“diagram”这件事从“画图”变成“写图”并用一个“diagram-design”的方式把它们系统化管理起来。这篇内容不是介绍某一个炫酷的绘图网站而是分享我如何用代码化、文本化、可版本化的方式来设计和管理项目里所有图的全过程包括工具选型、语法细节、自动化渲染、CI集成以及我踩过的那些坑。如果你需要给流程图、时序图、ER图、架构图找个可持续维护的产出方案这篇文章应该能帮你省下不少时间。1. 项目概述与核心场景为什么我需要 diagram-design1.1 手绘图形工具的痛点之前我也习惯用WYSIWYG所见即所得类工具画图。界面友好、功能齐全、拖拽方便初期的确很爽。但真正到了项目里问题会一个接一个冒出来。首先是版本管理。图一旦用二进制格式保存Git diff基本是废的。同事改了图你只能看到“两个文件不同”却看不到改了哪条连线、哪个判断分支。代码评审的时候图片变更没法review大家只能口头确认“哦改了就改了”。其次是维护成本。架构图最大的特点就是常变常新。服务从3个变成8个消息队列从1个变成3个权限模块拆出去了缓存层换掉了……这种变化频率下手工维护的图几乎必然滞后。你会发现图上画的和线上跑的完全两回事而新来的同事还对着老图研究半天。还有一个很隐蔽的问题图与文字脱节。文档里写了流程描述另外又放一张流程图两者很容易不一致。改文字忘了改图或者改图忘了更新文字都有可能出现。1.2 代码化图表的优势与即时收益所以我把眼光投向了文本化绘图方案并把整套方法沉淀为“diagram-design”。核心思路很简单图和代码一样用文本定义、用文件存储、用工具渲染。图的基本单元就是一行行可读的文本而不是不可解析的图形对象。这个思路带来的收益非常直接。第一图变成了代码的一部分可以和源码一起提交、一起review。每一次改动都能看到具体的差异评审者能指出“你这个分支少了返回路径”或“这俩节点之间的连线语义不对”工作流顺畅很多。第二维护变得像改代码一样可控。不用打开图形界面直接用编辑器改文本改完重新渲染几秒钟出一张新图。不再存在“图忘了改”的借口因为图和代码在同一个仓库里。第三自动化变得顺理成章。文档构建、CI检查、批量导出都可以通过命令行完成。图不再是一个孤立的产物而是可以嵌入到在线文档、API说明、发布会材料里的动态资源。提示这种方法适合的图是有明确结构和逻辑关系的图比如流程、依赖、时序、状态、ER、架构。而偏视觉表达、重艺术效果的图比如运营海报里的信息图仍然需要专业的图形设计工具。1.3 这个方向究竟适合哪些人如果你属于下面任意一类文本化绘图几乎可以立刻成为你的长期选择后端或全栈开发需要经常在文档里画架构图、调用链图。技术负责人或架构师需要维护一份能长期跟随项目演进的系统图集。运维或DevOps工程师需要把部署拓扑、CI流程、告警链路可视化。喜欢用Markdown写文档的人希望图表能直接嵌入写作流程。以及所有对“图会过期”这件事有过焦虑的人。2. 文本化图形工具选型Mermaid、Graphviz和PlantUML怎么选2.1 主流方案对比市面上主流的文本化绘图工具不少我重点对比了三个方向Mermaid、Graphviz、PlantUML。另外像D2、Excalidraw的文本模式也在发展但生态成熟度还差一些。为了不让你看概念看到头大我用一张表把关键差异列出来特性MermaidGraphvizPlantUML语法风格类似Markdown上手快声明式DOT语法偏底层类似Java的描述方式擅长图形流程图、时序图、甘特图、状态图依赖关系图、树形结构、自动布局时序图、用例图、活动图内置布局引擎简单适合中小图强大自动布局能力突出中等依赖外部布局输出格式SVG、PNG、PDFSVG、PNG、PDF、PostScript等SVG、PNG、LaTeX等文档生态GitHub原生支持Markdown集成极佳老牌被大量工具链引用老牌插件多学习成本低中高中这里说的只是“偏向”不代表Graphviz不能画流程图也不代表Mermaid不能画依赖关系图。只是每个工具在设计之初侧重点不同实际使用体验差别就会很大。2.2 我的选择逻辑不止看语法还要看维护成本我把Mermaid作为日常主选Graphviz作为补充。原因有几个。Mermaid的语法足够贴近自然语言。一张流程图写出来哪怕没渲染光看文本也能猜到大概结构。这个特性太重要了。因为图的生命周期里被阅读的次数远远大于被编辑的次数。一个能“读”的图定义比一张只能“看”的图片更有价值。Graphviz的价值在于布局引擎。遇到那种节点多、连线复杂、手工怎么摆都乱的依赖关系图DOT语言配合自动布局往往一击即中而且它处理大图的能力明显优于Mermaid。所以我保留它用于特殊场景。PlantUML也很强尤其是时序图和用例图语法表达非常清晰。我之所以没把它当主力是因为在Markdown集成、以及轻量级文档场景里Mermaid的生态更顺手。不过如果你所在的团队已经深度使用PlantUML不必强行切换能用得好就是好工具。经验之谈工具选型不追求“哪个最强”而是追求“哪个最不容易烂在仓库里”。团队里人人能编辑、人人能看懂远比某个工具表达能力多出几个功能重要。2.3 目录结构与版本管理策略既然把图当作代码管理目录结构就得提前设计好。我的习惯是docs/ ├── diagrams/ │ ├── system/ │ │ ├── auth-flow.mmd │ │ ├── order-flow.mmd │ │ └── deploy-topology.dot │ ├── database/ │ │ ├── user-er.mmd │ │ └── order-er.mmd │ └── assets/ │ ├── auth-flow.svg │ ├── order-flow.svg │ ├── user-er.svg │ └── deploy-topology.svg └── README.md源文件.mmd/.dot进入版本库生成的图片.svg/.png可以按需进入版本库。如果项目的文档网站需要静态图片建议把生成图片也提交到仓库如果只是本地或CI临时使用就可以加入忽略列表。这么做最大的好处是任何人通过源文件都能重建图而图片只是“产物”。即便有段时间没人去重新渲染也知道源头在哪、怎么改。3. 核心绘图细节流程图、时序图与ER图的表达要点3.1 流程图用最少的语法表达清晰逻辑流程图是我在“diagram-design”里用得最频繁的类型。Mermaid的流程图语法有几个核心点必须掌握。首先是节点与流向。一个最小流程只需要两样东西节点定义和连线。graph TD A[收到订单] -- B{库存充足?} B -- 是 -- C[扣减库存] B -- 否 -- D[拒绝订单] C -- E[生成发货单]这里graph TD表示从上到下的布局。节点有几种形状方括号表示矩形节点花括号表示判断节点圆括号表示圆角节点双层括号表示圆形节点。日常使用中方括号和花括号占了绝大多数。其次是子图。当流程复杂以后建议用subgraph把流程分组这样渲染出来的图会有清晰的视觉边界。graph TB subgraph 订单处理 A[创建订单] -- B[支付] B -- C[发货] end subgraph 售后流程 C -- D[发起售后] D -- E[审核] end子图一定要起有意义的名字否则渲染出来就是一堆框框读者根本不知道哪个阶段对应什么业务模块。然后是连线的语义。默认连线是实线箭头你可以在连接线上加文字也可以定义虚线、加粗线。比如A --|包含优惠券| B[计算金额] A -.-|异步通知| C[发送短信] A |异常告警| D[通知运维]一句话总结流程图的核心不是画得好看而是把分支、汇合、循环都表达完整。写完以后自己沿着流向走一遍看看有没有逻辑缺口。注意Mermaid中箭头文字里的中文是可以正常渲染的但要注意有些编辑器的旧版本可能对中文支持不完整。如果发现中文乱码优先检查渲染工具版本而不是急于改语法。3.2 时序图与状态图把时间线和状态变化画明白时序图是表达接口调用关系、消息传递顺序的利器。Mermaid的sequenceDiagram语法非常直白sequenceDiagram participant U as 用户端 participant S as 服务端 participant D as 数据库 U-S: 发送登录请求 S-D: 查询用户信息 D--S: 返回用户数据 S--U: 返回登录结果这里有几个细节值得说明。participant是参与者的声明。可以用简写别名让文本更简洁渲染出来后显示的是别名。消息类型有几种实线箭头-表示同步调用虚线箭头--表示返回结果还有-)表示异步消息。在描述分布式系统的时候同步和异步一定要区分开否则别人看图容易误会调用方式。状态图则适合描述对象的状态流转。举一个常见的例子stateDiagram-v2 [*] -- 待支付 待支付 -- 已支付: 支付成功 待支付 -- 已取消: 用户取消 已支付 -- 已发货: 商家发货 已发货 -- 已完成: 用户确认 已取消 -- [*]在真实项目中状态图特别适合用来对齐产品逻辑。很多需求歧义在看状态图时会突然暴露出来比如漏掉某个异常状态、漏了某个回退动作。画完状态图以后可以对着产品经理逐条确认比纯文字描述效率高得多。3.3 ER图与架构图数据结构与系统组成的表达方法ER图在数据库设计文档里必不可少。Mermaid的erDiagram语法可读性很好erDiagram USER ||--o{ ORDER : places ORDER ||--|{ ORDER_ITEM : contains ORDER_ITEM }o--|| PRODUCT : references这里||表示“恰好一个”o{表示“零个或多个”|{表示“一个或多个”。这种接近实体关系模型原生的表达方式能让团队成员一眼明白表之间的基数关系。架构图方面我倾向把Mermaid的graph和Graphviz的DOT配合使用。简单的服务分层用Mermaid就能搞定复杂的系统拓扑比如超过20个节点、大量双向依赖直接用Graphviz的自动布局反而更省力。Graphviz的DOT语法入门示例digraph G { rankdirLR; node [shapebox]; 前端服务 - API网关; API网关 - 用户服务; API网关 - 订单服务; 订单服务 - 数据库; }这里rankdirLR表示从左到右布局。Graphviz最厉害的地方在于你不需要手动指定节点的坐标它通过内置布局算法自动计算对复杂依赖图的处理非常成熟。4. 实操过程从空白目录到第一张图的完整路线4.1 环境准备与安装环境准备是整套工作流里最无趣但最关键的一步。我自己在macOS上开发Windows和Linux的安装方式略有差异但原理相同。首先安装Node.js环境因为Mermaid的命令行工具基于Node生态。然后安装渲染工具npm install -g mermaid-js/mermaid-cli如果是在项目里维护更推荐作为开发依赖安装npm install --save-dev mermaid-js/mermaid-cliGraphviz在大多数系统里可以直接通过包管理器安装# macOS brew install graphviz # Ubuntu sudo apt install graphviz # Windows choco install graphviz装完后检查一下版本确认命令可用mmdc --version dot -V这里有个经验mmdc是Mermaid的命令行客户端本质上是调用无头浏览器做渲染所以第一次运行可能会比较慢。如果长时间卡住可以检查网络代理配置以及本地是否缺少Puppeteer所需的浏览器依赖。4.2 从零到第一张图新建一个最简单的Mermaid文件命名为demo.mmdgraph TD A[开始] -- B{判断条件} B --|成立| C[执行操作] B --|不成立| D[结束]然后运行mmdc -i demo.mmd -o demo.svg打开demo.svg第一张图就出来了。这里我建议默认输出SVG而不是PNG。SVG是矢量格式不管放大缩小都不会糊在文档系统里嵌入也更灵活。如果要发到聊天软件再转一份PNG也不迟。等你熟悉了基本流程我建议做一个Makefile或者npm scripts来管理所有图的构建。比如在package.json里定义{ scripts: { diagram:build: mmdc -i docs/diagrams/system/*.mmd -o docs/diagrams/assets/ } }这样一条命令就能把整个diagrams目录下的源文件全部重新渲染。4.3 自动化嵌入文档、批量渲染与CI集成文本化绘图真正发挥威力是在接入自动化之后。第一步是嵌入文档。我写技术文档基本都用MarkdownMermaid在GitHub和很多Markdown编辑器中都能直接渲染。这意味着存储在仓库里的.md文件可以原生携带图表不需要截图、不需要贴图片链接。如果文档系统不支持Mermaid原生渲染可以提前渲染成SVG再用相对路径方式引用![订单流程](./diagrams/assets/order-flow.svg)第二步是批量渲染。当图的数量多了以后建议设计一个全局脚本。我自己的做法是把所有源文件统一命名使用*.mmd后缀脚本运行时扫描整个目录自动生成对应文件名for file in docs/diagrams/system/*.mmd; do mmdc -i $file -o docs/diagrams/assets/$(basename $file .mmd).svg done第三步是CI校验。在代码提交阶段自动检查图是否可以正常渲染。很多图的问题只有在渲染时才会暴露比如某个节点ID重复、某条连线指向了不存在的节点。把这些检查纳入CI可以有效避免图在线上裂开。一个简化的CI步骤概念如下- 拉取仓库代码 - 安装渲染工具 - 执行批量渲染脚本 - 将生成的SVG上传到文档平台 - 如果渲染失败则终止构建并提示修改源文件经验图的数量不要等多了才做自动化。哪怕只有三五张图提前把脚本固定下来后面每个新图都会自动走同一套流程维护负担几乎为零。5. 常见问题与排查建议渲染错误和布局异常的解决实录5.1 高频错误对照表我在用文本化绘图的过程中踩过不少坑这里把出现频率最高的整理成一张速查表。现象常见原因解决办法中文显示为方框渲染字体缺少中文字体配置在Mermaid配置中指定支持中文的字体或安装系统字体渲染超时或卡死图太大、节点过多导致浏览器引擎压力过大拆分子图减少单文件节点数量优先输出SVG连线乱飞交叉节点命名混乱、布局方向未指定明确指定布局方向使用子图分组必要时改用Graphviz语法报错找不出问题引号未转义、特殊字符混入用引号包裹节点文本检查{}、[]等特殊字符生成的图与预览不一致本地编辑器插件与CLI版本不同统一使用CLI渲染避免依赖编辑器内置预览批量脚本输出文件为空源文件路径包含空格或特殊字符用引号包住路径重命名为纯英文路径5.2 布局异常与乱码问题的实战排查有一次我画一张部署架构图节点不算多但渲染出来的连线非常乱两个子图之间的线交叉成一片。排查下来发现问题出在节点的命名上。我当时用了类似A-1、B-2这种命名括号里还带了空格和特殊符号。Mermaid对ID的解析和文本的解析是两套逻辑一旦ID里混入了特殊字符布局就容易被干扰。解决办法是ID一律使用纯英文和下短线比如auth_service、order_api。需要展示给读者看的文案放在方括号或括号标签里不让它参与ID解析。这个习惯养成以后布局异常减少很多。乱码问题则更多发生在中文字体环节。Mermaid CLI默认渲染时如果系统里没有合适的中文字体输出的SVG里中文就成方框。解决方式是写一个配置文件{ theme: default, fontFamily: Noto Sans CJK SC, PingFang SC, Microsoft YaHei, sans-serif }然后在命令里指定配置mmdc -i demo.mmd -o demo.svg -c mermaid-config.json这样中文字体就能正确渲染。5.3 避免返工的三种思路画图本身不难难的是让图长期不出问题。我总结了三条经验。第一不要为图而图。每张图都应该回答一个具体的问题描述一次登录流程展示服务依赖说明数据库关系把问题写清楚再画。一张目的不明确的图最后大概率会被删掉。第二保持小图思维。单张图的节点尽量控制在15个以内。超过这个量无论用什么工具都会面临可读性下降的问题。图太大就拆拆成总览图加细节图比一张巨无霸更实用。第三图要和文档同步。每个图都应该在文档中有引用位置说明它对应的业务场景。孤立挂在目录里的图无人维护的概率极高。6. 后续扩展如何把一套图表体系做得更深当基本工作流跑通以后可以继续往这几个方向扩展。一是模板化复用。我的项目里很多图的结构是相似的比如不同服务的调用链路。把公共结构提取成模板用脚本生成不同实例会省掉大量重复工作。虽然刚开始建模板需要一些成本但团队一旦用起来整体效率提升是明显的。二是从代码反推图形。后端服务之间的依赖关系可以通过扫描代码中的接口调用或包依赖关系来生成。现在很多静态分析工具已经能输出关系数据配合文本化绘图工具能够做到“代码变了图跟着变”。三是与文档平台深度结合。很多在线文档系统支持通过API上传SVG或者直接渲染Mermaid代码块。把生成的图表自动同步到项目主页、接口文档里图和线上文档就彻底绑定了。做过一段时间之后你会发现最珍贵的不是某一张图画得多漂亮而是整套图变成了项目知识库的一部分。新同事入职看流程图了解业务排查问题看时序图确认调用链设计评审看ER图对齐数据结构这些场景下的图价值远超一张摆拍架构图。我个人在实际操作中最深的一个体会是不要迷信任何单一工具也不要过早追求复杂的模板。先把三张图画好再思考怎么自动化最后才谈平台集成。一步步来这套体系自然会长成适合你团队的样子。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑