Files
nanocode/skills/hand-drawn-diagram/examples/generate_blog_arch.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

217 lines
9.5 KiB
Python

#!/usr/bin/env python3
"""
生成 Personal Blog 详细技术架构图
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from generator import DiagramGenerator, Component, Connection
W, H = 1800, 1250
gen = DiagramGenerator(style="sketch")
# ============ 组件定义 ============
# 第1行: 用户层
user = Component(name="用户浏览器", type="cloud", x=800, y=70, width=160, height=50, shape="cloud")
# 第2行: 网关层
nginx = Component(name="Nginx 反向代理", type="cloud", x=800, y=170, width=160, height=50, shape="double")
# 第3行: 核心框架
react = Component(name="React + Vite", type="frontend", x=280, y=290, width=160, height=50)
tailwind = Component(name="Tailwind CSS", type="frontend", x=280, y=390, width=160, height=50)
fastapi = Component(name="FastAPI", type="backend", x=800, y=290, width=160, height=50, shape="hexagon")
uvicorn = Component(name="Uvicorn ASGI", type="backend", x=800, y=390, width=160, height=50)
playwright = Component(name="Playwright", type="backend", x=1360, y=290, width=160, height=50)
bs4 = Component(name="BeautifulSoup4", type="backend", x=1360, y=390, width=160, height=50)
# 第4行: 前端页面
pages = Component(name="前端页面\nHome / Post / Dashboard / Login", type="frontend", x=280, y=500, width=180, height=60)
# 第5行: API 路由层
auth_api = Component(name="Auth API\n(JWT认证)", type="security", x=400, y=630, width=140, height=60)
posts_api = Component(name="Posts API\n(文章CRUD)", type="backend", x=570, y=630, width=140, height=60)
cats_api = Component(name="Categories API\n(分类管理)", type="backend", x=740, y=630, width=140, height=60)
tags_api = Component(name="Tags API\n(标签管理)", type="backend", x=910, y=630, width=140, height=60)
images_api = Component(name="Images API\n(图片上传/缩略图)", type="backend", x=1080, y=630, width=150, height=60)
settings_api = Component(name="Settings API\n(博客设置)", type="backend", x=1260, y=630, width=140, height=60)
# 第6行: 采集与工具
crawl_api = Component(name="Crawl API\n(网页采集)", type="backend", x=1360, y=500, width=160, height=60)
stealth = Component(name="Playwright\nStealth", type="security", x=1530, y=390, width=130, height=50)
# 第7行: 数据访问层
sqlalchemy = Component(name="SQLAlchemy ORM", type="backend", x=800, y=770, width=160, height=50)
pydantic = Component(name="Pydantic\n数据校验", type="generic", x=280, y=770, width=160, height=50)
thumb = Component(name="Pillow\n缩略图生成", type="backend", x=1080, y=770, width=150, height=50)
# 第8行: 存储层
sqlite = Component(name="SQLite\nblog.db", type="database", x=800, y=890, width=160, height=60, shape="cylinder")
file_storage = Component(name="文件存储\nuploads/ thumbnails/", type="cloud", x=1080, y=890, width=150, height=60, shape="cloud")
# 第9行: 安全与依赖
jwt = Component(name="python-jose\nJWT令牌", type="security", x=570, y=1010, width=140, height=50)
passlib = Component(name="passlib\n密码哈希", type="security", x=740, y=1010, width=140, height=50)
python_multipart = Component(name="python-multipart\n表单解析", type="generic", x=910, y=1010, width=140, height=50)
# 外部
external_site = Component(name="外部网站\n(SPA/静态)", type="cloud", x=1530, y=170, width=130, height=50, shape="cloud")
components = [
user, nginx,
react, tailwind, fastapi, uvicorn, playwright, bs4,
pages,
auth_api, posts_api, cats_api, tags_api, images_api, settings_api,
crawl_api, stealth,
sqlalchemy, pydantic, thumb,
sqlite, file_storage,
jwt, passlib, python_multipart,
external_site,
]
# ============ 连接定义 ============
connections = [
# 用户 → 网关 → 前后端
Connection("用户浏览器", "Nginx 反向代理", style="solid", label="HTTP/HTTPS"),
Connection("Nginx 反向代理", "React + Vite", style="solid", label=""),
Connection("Nginx 反向代理", "FastAPI", style="solid", label="API请求"),
# 前端内部
Connection("React + Vite", "Tailwind CSS", style="dashed", label=""),
Connection("Tailwind CSS", "前端页面\nHome / Post / Dashboard / Login", style="dashed", label=""),
Connection("前端页面\nHome / Post / Dashboard / Login", "Nginx 反向代理", style="solid", label="REST API"),
# 后端核心
Connection("FastAPI", "Uvicorn ASGI", style="dashed", label="运行"),
Connection("Uvicorn ASGI", "Auth API\n(JWT认证)", style="dashed", label=""),
Connection("Uvicorn ASGI", "Posts API\n(文章CRUD)", style="dashed", label=""),
Connection("Uvicorn ASGI", "Categories API\n(分类管理)", style="dashed", label=""),
Connection("Uvicorn ASGI", "Tags API\n(标签管理)", style="dashed", label=""),
Connection("Uvicorn ASGI", "Images API\n(图片上传/缩略图)", style="dashed", label=""),
Connection("Uvicorn ASGI", "Settings API\n(博客设置)", style="dashed", label=""),
Connection("Uvicorn ASGI", "Crawl API\n(网页采集)", style="dashed", label=""),
# 采集
Connection("Crawl API\n(网页采集)", "Playwright", style="solid", label="SPA采集"),
Connection("Crawl API\n(网页采集)", "BeautifulSoup4", style="solid", label="HTML解析"),
Connection("Crawl API\n(网页采集)", "Playwright\nStealth", style="dashed", label="反检测"),
Connection("Playwright", "外部网站\n(SPA/静态)", style="solid", label="渲染/抓取"),
# 数据访问
Connection("Auth API\n(JWT认证)", "SQLAlchemy ORM", style="solid", label=""),
Connection("Posts API\n(文章CRUD)", "SQLAlchemy ORM", style="solid", label=""),
Connection("Categories API\n(分类管理)", "SQLAlchemy ORM", style="solid", label=""),
Connection("Tags API\n(标签管理)", "SQLAlchemy ORM", style="solid", label=""),
Connection("Images API\n(图片上传/缩略图)", "SQLAlchemy ORM", style="solid", label=""),
Connection("Settings API\n(博客设置)", "SQLAlchemy ORM", style="solid", label=""),
Connection("Crawl API\n(网页采集)", "SQLAlchemy ORM", style="solid", label="写入文章"),
# ORM → 数据库
Connection("SQLAlchemy ORM", "SQLite\nblog.db", style="solid", label="SQL"),
# 图片处理
Connection("Images API\n(图片上传/缩略图)", "Pillow\n缩略图生成", style="dashed", label="缩略"),
Connection("Pillow\n缩略图生成", "文件存储\nuploads/ thumbnails/", style="solid", label="保存"),
# 安全
Connection("Auth API\n(JWT认证)", "python-jose\nJWT令牌", style="dashed", label="签发/验证"),
Connection("Auth API\n(JWT认证)", "passlib\n密码哈希", style="dashed", label="bcrypt"),
Connection("Images API\n(图片上传/缩略图)", "python-multipart\n表单解析", style="dashed", label="上传"),
# 校验层
Connection("Pydantic\n数据校验", "FastAPI", style="dashed", label="Request/Response"),
]
# ============ 容器分组 ============
from generator import Container
containers = [
Container(name="前端层", x=160, y=240, width=320, height=360,
color="#22d3ee"),
Container(name="后端 API 层", x=370, y=570, width=1060, height=160,
color="#34d399"),
Container(name="数据采集层", x=1320, y=240, width=360, height=360,
color="#fbbf24"),
Container(name="数据存储层", x=680, y=840, width=600, height=160,
color="#a78bfa"),
Container(name="安全与工具层", x=490, y=960, width=600, height=130,
color="#fb7185"),
]
# ============ 生成 SVG ============
svg_path = "/home/ubuntu/.nanobot/workspace/skills/hand-drawn-diagram/examples/personal-blog-architecture.svg"
html_path = "/home/ubuntu/.nanobot/workspace/skills/hand-drawn-diagram/examples/personal-blog-architecture.html"
gen.create_architecture(
title="Personal Blog 技术架构",
components=components,
connections=connections,
output=svg_path,
format="svg",
containers=containers,
auto_layout=False,
)
# 嵌入到 HTML
with open(svg_path, "r", encoding="utf-8") as f:
svg_content = f.read()
# 添加画布尺寸
svg_content = svg_content.replace(
'<svg xmlns="http://www.w3.org/2000/svg"',
f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}"'
)
# 添加背景矩形
bg_rect = f'<rect x="0" y="0" width="{W}" height="{H}" fill="#020617"/>'
# 在第一个 <defs> 之后插入背景
svg_content = svg_content.replace("</defs>", f"</defs>\n{bg_rect}")
html_template = f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Personal Blog 技术架构图</title>
<style>
body {{
margin: 0;
background: #020617;
font-family: 'Noto Sans Mono CJK SC', 'JetBrains Mono', 'WenQuanYi Zen Hei Mono', monospace;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}}
h1 {{
color: #e2e8f0;
margin: 24px 0 6px;
font-size: 24px;
}}
h2 {{
color: #64748b;
margin: 0 0 20px;
font-size: 14px;
font-weight: normal;
}}
#diagram {{
width: 98vw;
overflow: auto;
padding-bottom: 40px;
}}
.info {{
color: #475569;
font-size: 12px;
margin-top: 16px;
}}
</style>
</head>
<body>
<h1>📝 Personal Blog 技术架构图</h1>
<h2>FastAPI + React + SQLite | 手绘风格 SVG | 零 AI Token 成本</h2>
<div id="diagram">
{svg_content}
</div>
<div class="info">生成时间: 2026-04-26 | 使用 hand-drawn-diagram 技能</div>
</body>
</html>
'''
with open(html_path, "w", encoding="utf-8") as f:
f.write(html_template)
print(f"✅ SVG 已生成: {svg_path}")
print(f"✅ HTML 已生成: {html_path}")