从代码生成 PlantUML:5 种常见自动化方式

puml.online

与其一张一张手画类图,不如让代码自己生成。这篇整理 5 种常见方案:TypeDoc、JSDoc、Doxygen、SchemaCrawler、Python 自定义 walker。

方案 1:TypeDoc(TypeScript)

1
npm install --save-dev typedoc
1
2
3
4
5
// typedoc.json
{
"entryPoints": ["src/index.ts"],
"json": "docs/api/typedoc.json"
}
1
npx typedoc --options typedoc.json

输出 typedoc.json —— 包含所有类型信息。

Typedo 默认不带 PlantUML

但有插件:

1
npm install --save-dev typedoc-plantuml
1
2
3
4
// typedoc.json
{
"plugin": ["typedoc-plantuml"]
}

自动生成 class 图(手动)

要更直接的方案,写一个 TypeDoc post-render 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import * as fs from 'fs';

const data = JSON.parse(fs.readFileSync('docs/api/typedoc.json', 'utf-8'));

const classes = data.children.filter((c: any) => c.kind === 'class');

let plantuml = '@startuml\n';
for (const c of classes) {
const name = c.name.replace(/([<>])/g, '\\$1');
plantuml += `class ${name} {\n`;
for (const m of c.children) {
if (m.kind === 'method') {
plantuml += ` +${m.name}()\n`;
} else if (m.kind === 'property') {
plantuml += ` +${m.name}: ${m.type || 'unknown'}\n`;
}
}
plantuml += '}\n';
}

for (const c of classes) {
for (const m of c.children) {
if (m.kind === 'constructor') {
// 引用的关联
}
}
}
plantuml += '@enduml\n';

fs.writeFileSync('docs/class/auto.puml', plantuml);
1
2
npx typedoc --options typedoc.json
node scripts/gen-plantuml.ts

方案 2:JSDoc + 类图

JSDoc 通过 @inheritDoc 和 tags 表达类型,但不直接生成 PlantUML

1
2
3
4
5
6
7
8
9
10
/**
* A class.
* @class MyClass
* @param {string} name
*/
class MyClass {
constructor(name) {}
/** Get name. */
getName() { return this.name }
}

生成的 jsdoc HTML 里没有 PlantUML,需要额外插件:

1
npm install --save-dev jsdoc-to-plantuml
1
2
npx jsdoc src -d=docs
npx jsdoc-to-plantuml docs/jsdoc.json >docs/class/auto.puml

方案 3:Doxygen(C++ / Java / 注释语言)

1
sudo apt-get install doxygen graphviz
1
2
3
4
5
6
7
# Doxyfile
GENERATE_LATEX = NO
HAVE_DOT = YES
GENERATE_GRAPH = YES
CALL_GRAPH = YES
PLANTUML_PATH = plantuml.jar
JAVADOC_AUTOBRIEF = YES
1
2
3
doxygen -g Doxyfile
sed -i 's/#PLANTUML_PATH.*/PLANTUML_PATH = plantuml.jar/' Doxyfile
doxygen Doxyfile

Doxygen 直接调用 PlantUML 渲染 @dot / @startuml 块,附 HTML。

C++ 注释里嵌入 @startuml

1
2
3
4
5
6
7
8
9
10
/**
* @brief Service endpoint.
*
* @startuml
* class A {
* +foo()
* }
* B --> A
* @enduml
*/

Doxygen 渲染输出 SVG 自动嵌入。

方案 4:SchemaCrawler(数据库 → PlantUML)

数据库 schema 是天然的关系图。SchemaCrawler 可以生成 SQL DDL、PlantUML、Mermaid:

1
2
3
4
5
6
7
8
9
10
java -jar schemacrawler.jar \
--server=postgresql \
--host=db.internal \
--database=appdb \
--user=app \
--password=*** \
--info-level=standard \
--command=schema \
--output-format=puml \
--output-file=docs/er.puml

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@startuml
hide circle

entity "users" {
*id : BIGINT <<PK>>
*email : VARCHAR(255)
...
}

entity "orders" {
*id : BIGINT <<PK>>
*user_id : BIGINT <<FK>>
}

users - orders : user_id

@enduml

集成到 CI

1
2
3
4
5
6
7
8
9
10
- name: Generate DB schema
run: |
java -jar schemacrawler.jar \
--server=postgresql \
--host=localhost \
--database=appdb \
--user=app \
--password=$DB_PWD \
--output-format=puml \
--output-file=docs/db/er.puml

每次数据库 schema 变更,自动生成新的 ER 图。

方案 5:Python 自定义 walker

Python 写一个 AST walker:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import ast

def walk_class(node):
"""Generate PlantUML for a Python class."""
out = f'class {node.name} {{\n'
# walk methods
for item in node.body:
if isinstance(item, ast.FunctionDef):
args = ', '.join(a.arg for a in item.args.args)
visibility = '+' if not item.name.startswith('_') else '-'
out += f' {visibility}{item.name}({args})\n'
elif isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
out += f' +{item.target.id}: {ast.unparse(item.annotation)}\n'
out += '}\n'
return out


def walk_module(filepath):
"""Walk a Python file and produce PlantUML."""
tree = ast.parse(open(filepath).read())
output = '@startuml\n'
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
output += walk_class(node)
output += '@enduml\n'
return output


if __name__ == '__main__':
import sys
print(walk_module(sys.argv[1]))
1
2
3
4
5
6
7
8
9
$ python walk.py models/user.py
@startuml
class User {
+name: str
+email: str
+login()
+logout()
}
@enduml

用 inspect 简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import inspect
import types

def generate(cls):
out = f'class {cls.__name__} {{\n'
for name, value in inspect.getmembers(cls):
if name.startswith('_'):
continue
if isinstance(value, types.FunctionType):
sig = inspect.signature(value)
out += f' +{name}{sig}\n'
elif isinstance(value, type):
out += f' +{name}: {value.__name__}\n'
out += '}\n'
return out


if __name__ == '__main__':
from user import User
print(generate(User))

运行后实时生成。

实战:CI 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# .github/workflows/gen-docs.yml
name: Generate docs
on:
push:
paths: ['src/**', 'app/**', 'lib/**']
schedule:
- cron: '0 6 * * *' # daily

jobs:
gen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: TypeDoc (TypeScript)
run: |
npm ci
npx typedoc --options typedoc.json
node scripts/gen-plantuml.ts
working-directory: src/typescript

- name: JSDoc (JS)
run: |
npm ci
npx jsdoc -d=docs src
npx jsdoc-to-plantuml docs/jsdoc.json >docs/class/js.puml
working-directory: src/javascript

- name: Doxygen (C++)
run: |
doxygen Doxyfile
working-directory: src/cpp

- name: SchemaCrawler (DB)
run: |
java -jar schemacrawler.jar \
--server=postgresql \
--host=$DB_HOST --database=appdb \
--user=app --password=$DB_PWD \
--output-format=puml \
--output-file=docs/db/er.puml

- name: Auto-commit
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add docs/**/*.puml
git commit -m "docs: regenerate class diagrams [skip ci]" || exit 0
git push

实战:类图只展示「Public API」

1
2
3
// scripts/gen-plantuml.ts
// 只导出 public 字段
const classes = data.children.filter((c: any) => c.flags.isPublic);

避免公开 API 类图被内部字段淹没。

反模式

1. 一张图包含所有内容

1
2
3
// ❌ 全类图
const allClasses = data.children.filter((c: any) => c.kind === 'class');
// 输出一个 200 个类的 SVG

拆 6-8 张主题图(核心 API / 数据模型 / webhooks / 等)。

2. 自动图覆盖手画图

自动生成的图是参考。手画图才是「真正传递意图」的。两者并存。

3. CI 每天生成但没评审

1
2
schedule:
- cron: '0 6 * * *'

每天 commit 产生噪声;不开 cron / 不写说明等于 CI 拖累。

4. 自动生成覆盖了定制图

自动图里覆盖你的 nice 图,损失信号。让 CI 把定制图当作可合并资产,自动图放在专门的目录(如 docs/auto/)。

评审 checklist

自动生成

  • 输出的图清晰可读?
  • 拆成多个主题(不要全类图)?
  • 在 CI 每周生成?

手画

  • 写明意图的图保留为手画?
  • 自动图不去覆盖定制图?

一句话总结

从代码到 PlantUML 不必手画一遍。5 个工具(TypeDoc / JSDoc / Doxygen / SchemaCrawler / Python walker) 让 CI 自动出图,配合手画图表达「意图」,文档就长久新鲜。

  • 标题: 从代码生成 PlantUML:5 种常见自动化方式
  • 作者: puml.online
  • 创建于 : 2026-07-29 16:35:00
  • 更新于 : 2026-08-14 21:34:29
  • 链接: https://puml.online/blog/plantuml-from-code/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。