写代码画图这件事,我一开始是拒绝的。用了那么多年鼠标,拖拖拽拽不香吗?直到某一天,我需要给一个持续迭代的模块画架构图,改了三次需求,我重画了四遍,而且文档里那张图和线上代码已经差了三个版本。也就是在那一刻,我决定把“diagram”这件事从“画图”变成“写图”,并用一个“diagram-design”的方式把它们系统化管理起来。
这篇内容不是介绍某一个炫酷的绘图网站,而是分享我如何用代码化、文本化、可版本化的方式,来设计和管理项目里所有图的全过程,包括工具选型、语法细节、自动化渲染、CI集成,以及我踩过的那些坑。如果你需要给流程图、时序图、ER图、架构图找个可持续维护的产出方案,这篇文章应该能帮你省下不少时间。
1. 项目概述与核心场景:为什么我需要 diagram-design
1.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的文本模式也在发展,但生态成熟度还差一些。
为了不让你看概念看到头大,我用一张表把关键差异列出来:
| 特性 | Mermaid | Graphviz | PlantUML |
|---|---|---|---|
| 语法风格 | 类似Markdown,上手快 | 声明式DOT语法,偏底层 | 类似Java的描述方式 |
| 擅长图形 | 流程图、时序图、甘特图、状态图 | 依赖关系图、树形结构、自动布局 | 时序图、用例图、活动图 |
| 内置布局引擎 | 简单,适合中小图 | 强大,自动布局能力突出 | 中等,依赖外部布局 |
| 输出格式 | SVG、PNG、PDF | SVG、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 { rankdir=LR; node [shape=box]; "前端服务" -> "API网关"; "API网关" -> "用户服务"; "API网关" -> "订单服务"; "订单服务" -> "数据库"; }这里rankdir=LR表示从左到右布局。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.mmd:
graph 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集成
文本化绘图真正发挥威力,是在接入自动化之后。
第一步是嵌入文档。我写技术文档基本都用Markdown,Mermaid在GitHub和很多Markdown编辑器中都能直接渲染。这意味着存储在仓库里的.md文件可以原生携带图表,不需要截图、不需要贴图片链接。
如果文档系统不支持Mermaid原生渲染,可以提前渲染成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图对齐数据结构,这些场景下的图,价值远超一张摆拍架构图。
我个人在实际操作中最深的一个体会是:不要迷信任何单一工具,也不要过早追求复杂的模板。先把三张图画好,再思考怎么自动化,最后才谈平台集成。一步步来,这套体系自然会长成适合你团队的样子。