271 行
15 KiB
Python
271 行
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
生成文章资讯专属现代中式极简矢量封面图 (Concise & Advanced Article Covers Generator)
|
|
彻底杜绝文字介绍类图片,纯粹以现代中式浅色专业风空间几何、流光、细线窗棂与单字印章呈现。
|
|
尺寸 600x380,浅色专业底色 #FFFFFF / #F8FAFC,主色 #2563EB,辅色 #06B6D4 / #7C3AED。
|
|
"""
|
|
import os
|
|
|
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
TARGET_DIR = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'images', 'articles')
|
|
os.makedirs(TARGET_DIR, exist_ok=True)
|
|
|
|
COMMON_DEFS = '''
|
|
<linearGradient id="artBgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="#FFFFFF"/>
|
|
<stop offset="100%" stop-color="#F8FAFC"/>
|
|
</linearGradient>
|
|
<linearGradient id="artBlue" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="#2563EB"/>
|
|
<stop offset="100%" stop-color="#1D4ED8"/>
|
|
</linearGradient>
|
|
<linearGradient id="artCyan" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="#06B6D4"/>
|
|
<stop offset="100%" stop-color="#0EA5E9"/>
|
|
</linearGradient>
|
|
<linearGradient id="artPurple" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="#7C3AED"/>
|
|
<stop offset="100%" stop-color="#6366F1"/>
|
|
</linearGradient>
|
|
<linearGradient id="artGlass" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.92"/>
|
|
<stop offset="100%" stop-color="#F1F5F9" stop-opacity="0.7"/>
|
|
</linearGradient>
|
|
<pattern id="artLattice" width="24" height="24" patternUnits="userSpaceOnUse">
|
|
<path d="M 24 0 L 0 0 0 24" fill="none" stroke="#E2E8F0" stroke-width="0.75" stroke-dasharray="3,3"/>
|
|
</pattern>
|
|
<filter id="artShadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
<feDropShadow dx="0" dy="6" stdDeviation="10" flood-color="#0F172A" flood-opacity="0.08"/>
|
|
</filter>
|
|
<filter id="artGlow" x="-30%" y="-30%" width="160%" height="160%">
|
|
<feGaussianBlur stdDeviation="8" result="blur"/>
|
|
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
|
</filter>
|
|
'''
|
|
|
|
def make_art_seal(x, y, char):
|
|
return f'''
|
|
<g transform="translate({x}, {y})">
|
|
<rect width="24" height="24" rx="4" fill="#FEF2F2" stroke="#DC2626" stroke-width="1.2"/>
|
|
<rect x="2" y="2" width="20" height="20" rx="2" fill="none" stroke="#DC2626" stroke-width="0.5" stroke-opacity="0.5"/>
|
|
<text x="12" y="17" fill="#DC2626" font-family="'Noto Serif SC', 'SimSun', serif" font-size="12" font-weight="700" text-anchor="middle">{char}</text>
|
|
</g>
|
|
'''
|
|
|
|
def create_base_svg(seal_char, visual_elements):
|
|
return f'''<svg width="600" height="380" viewBox="0 0 600 380" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
{COMMON_DEFS}
|
|
</defs>
|
|
|
|
<rect width="600" height="380" rx="8" fill="url(#artBgGrad)"/>
|
|
<rect width="600" height="380" rx="8" fill="url(#artLattice)" opacity="0.6"/>
|
|
<rect x="1" y="1" width="598" height="378" rx="7" fill="none" stroke="#E2E8F0" stroke-width="1.5"/>
|
|
|
|
{make_art_seal(554, 20, seal_char)}
|
|
|
|
<g transform="translate(300, 190)">
|
|
{visual_elements}
|
|
</g>
|
|
</svg>'''
|
|
|
|
articles = {
|
|
# 首页 4 篇技术深度文章专属抽象封面
|
|
# 1. 缓存穿透、击穿与雪崩《三阶重滤与抗压漏斗》
|
|
'article_cache.svg': ('盾', '''
|
|
<ellipse cx="0" cy="70" rx="210" ry="75" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4"/>
|
|
<!-- 布隆过滤器多阶防御漏斗与位图拦截阵列 -->
|
|
<polygon points="0,-100 110,-55 0,-20 -110,-55" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.5" filter="url(#artShadow)"/>
|
|
<polygon points="0,-45 75,-10 0,18 -75,-10" fill="url(#artGlass)" stroke="#06B6D4" stroke-width="1.2"/>
|
|
<polygon points="0,5 45,26 0,46 -45,26" fill="url(#artBlue)"/>
|
|
<line x1="0" y1="-100" x2="0" y2="75" stroke="url(#artBlue)" stroke-width="2.5" stroke-dasharray="4,3"/>
|
|
<!-- 底部安全落盘数据持久层 -->
|
|
<circle cx="0" cy="75" r="22" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.5"/>
|
|
<circle cx="0" cy="75" r="8" fill="#2563EB" filter="url(#artGlow)"/>
|
|
<!-- 拦截击溃的异常请求粒子 -->
|
|
<circle cx="-85" cy="-35" r="4.5" fill="#DC2626"/>
|
|
<circle cx="85" cy="-35" r="4.5" fill="#DC2626"/>
|
|
<circle cx="-50" cy="15" r="3.5" fill="#06B6D4"/>
|
|
<circle cx="50" cy="15" r="3.5" fill="#2563EB"/>
|
|
'''),
|
|
|
|
# 2. MySQL LIMIT 深度分页《B+Tree 跳跃指针与延迟关联》
|
|
'article_mysql_page.svg': ('跃', '''
|
|
<ellipse cx="0" cy="65" rx="200" ry="70" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
|
|
<!-- B+Tree 索引阶梯基准线 -->
|
|
<line x1="-150" y1="45" x2="150" y2="45" stroke="#CBD5E1" stroke-width="1.5"/>
|
|
<g transform="translate(-100, 20)">
|
|
<rect x="-18" y="-18" width="36" height="36" rx="5" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.2"/>
|
|
<circle cx="0" cy="0" r="4" fill="#94A3B8"/>
|
|
</g>
|
|
<g transform="translate(-25, -5)">
|
|
<rect x="-18" y="-18" width="36" height="36" rx="5" fill="url(#artGlass)" stroke="#93C5FD" stroke-width="1.5"/>
|
|
<circle cx="0" cy="0" r="4" fill="#2563EB"/>
|
|
</g>
|
|
<!-- 高性能延迟关联跳跃抛物线 (由 3.8s 降至 12ms) -->
|
|
<path d="M -100 5 C -40 -85 50 -85 100 -15" fill="none" stroke="url(#artCyan)" stroke-width="3" stroke-linecap="round"/>
|
|
<g transform="translate(100, -15)" filter="url(#artShadow)">
|
|
<rect x="-24" y="-24" width="48" height="48" rx="8" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="0" r="8" fill="#FFFFFF" filter="url(#artGlow)"/>
|
|
<circle cx="0" cy="0" r="3" fill="#1D4ED8"/>
|
|
</g>
|
|
<circle cx="0" cy="-70" r="5.5" fill="#06B6D4" filter="url(#artGlow)"/>
|
|
'''),
|
|
|
|
# 3. XXL-JOB 分布式分片广播《全域广播与等距波纹》
|
|
'article_xxl_job.svg': ('播', '''
|
|
<!-- 同心全域广播波纹 -->
|
|
<ellipse cx="0" cy="0" rx="170" ry="85" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
|
|
<ellipse cx="0" cy="0" rx="115" ry="58" fill="none" stroke="#CBD5E1" stroke-width="1.2"/>
|
|
<ellipse cx="0" cy="0" rx="60" ry="30" fill="none" stroke="#93C5FD" stroke-width="1.5"/>
|
|
<!-- 中心分布式任务调度器 -->
|
|
<g filter="url(#artShadow)">
|
|
<circle cx="0" cy="0" r="30" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="0" r="12" fill="#FFFFFF"/>
|
|
<circle cx="0" cy="0" r="5" fill="#1D4ED8"/>
|
|
</g>
|
|
<!-- 环绕分片广播执行节点集群 -->
|
|
<circle cx="-150" cy="0" r="9" fill="url(#artGlass)" stroke="#06B6D4" stroke-width="1.8"/>
|
|
<circle cx="150" cy="0" r="9" fill="url(#artGlass)" stroke="#06B6D4" stroke-width="1.8"/>
|
|
<circle cx="0" cy="-75" r="8" fill="url(#artGlass)" stroke="#7C3AED" stroke-width="1.8"/>
|
|
<circle cx="0" cy="75" r="8" fill="url(#artGlass)" stroke="#7C3AED" stroke-width="1.8"/>
|
|
<circle cx="-105" cy="-45" r="6" fill="#2563EB"/>
|
|
<circle cx="105" cy="45" r="6" fill="#2563EB"/>
|
|
<circle cx="-105" cy="45" r="6" fill="#06B6D4"/>
|
|
<circle cx="105" cy="-45" r="6" fill="#7C3AED"/>
|
|
'''),
|
|
|
|
# 4. Redis 集群脑裂防御《双子星隔离与法定仲裁》
|
|
'article_redis_split.svg': ('防', '''
|
|
<!-- 网络分区虚线隔断 (Network Partition Boundary) -->
|
|
<line x1="0" y1="-115" x2="0" y2="115" stroke="#CBD5E1" stroke-width="1.8" stroke-dasharray="6,4"/>
|
|
<!-- 左侧:健康多数派集群节点 (Quorum Majority) -->
|
|
<g transform="translate(-85, 0)" filter="url(#artShadow)">
|
|
<circle cx="0" cy="0" r="52" fill="#EFF6FF" opacity="0.85"/>
|
|
<circle cx="0" cy="0" r="38" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="0" r="16" fill="#FFFFFF"/>
|
|
<circle cx="0" cy="0" r="7" fill="#1D4ED8"/>
|
|
<circle cx="-42" cy="-42" r="6" fill="#06B6D4"/>
|
|
<circle cx="42" cy="-42" r="6" fill="#06B6D4"/>
|
|
<circle cx="0" cy="52" r="6" fill="#2563EB"/>
|
|
</g>
|
|
<!-- 右侧:孤立少数派自动休眠 (Standby Sleep Mode) -->
|
|
<g transform="translate(85, 0)">
|
|
<circle cx="0" cy="0" r="44" fill="none" stroke="#E2E8F0" stroke-width="1.5" stroke-dasharray="4,4"/>
|
|
<circle cx="0" cy="0" r="28" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.5"/>
|
|
<circle cx="0" cy="0" r="10" fill="#94A3B8"/>
|
|
</g>
|
|
'''),
|
|
|
|
# 既有文章 8 篇
|
|
'article_microservice.svg': ('枢', '''
|
|
<ellipse cx="0" cy="50" rx="190" ry="75" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4"/>
|
|
<g transform="translate(-70, 0)" filter="url(#artShadow)">
|
|
<polygon points="0,-28 35,-10 0,10 -35,-10" fill="url(#artBlue)"/>
|
|
<polygon points="-35,-10 0,10 0,34 -35,14" fill="#1D4ED8"/>
|
|
<polygon points="0,10 35,-10 35,14 0,34" fill="#1E40AF"/>
|
|
</g>
|
|
<g transform="translate(70, 0)" filter="url(#artShadow)">
|
|
<polygon points="0,-28 35,-10 0,10 -35,-10" fill="url(#artCyan)"/>
|
|
<polygon points="-35,-10 0,10 0,34 -35,14" fill="#0891B2"/>
|
|
<polygon points="0,10 35,-10 35,14 0,34" fill="#0E7490"/>
|
|
</g>
|
|
<g transform="translate(0, -50)" filter="url(#artShadow)">
|
|
<polygon points="0,-24 28,-8 0,8 -28,-8" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.2"/>
|
|
<circle cx="0" cy="0" r="4" fill="#2563EB"/>
|
|
</g>
|
|
<path d="M -70 0 Q 0 -40 70 0" fill="none" stroke="url(#artBlue)" stroke-width="2" stroke-linecap="round"/>
|
|
<path d="M 70 15 Q 0 55 -70 15" fill="none" stroke="url(#artCyan)" stroke-width="1.8" stroke-dasharray="4,3"/>
|
|
'''),
|
|
|
|
'article_cross_app.svg': ('端', '''
|
|
<ellipse cx="0" cy="60" rx="180" ry="65" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
|
|
<g transform="translate(-65, -10)" filter="url(#artShadow)">
|
|
<rect x="-45" y="-65" width="90" height="130" rx="6" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.2"/>
|
|
<rect x="-35" y="-55" width="70" height="95" rx="3" fill="#FFFFFF" stroke="#E2E8F0"/>
|
|
<circle cx="0" cy="52" r="3" fill="#94A3B8"/>
|
|
</g>
|
|
<g transform="translate(60, 10)" filter="url(#artShadow)">
|
|
<rect x="-38" y="-75" width="76" height="150" rx="10" fill="#FFFFFF" stroke="#2563EB" stroke-width="1.5"/>
|
|
<circle cx="0" cy="-25" r="14" fill="url(#artBlue)"/>
|
|
</g>
|
|
<path d="M -20 -30 Q 15 -60 45 -40" fill="none" stroke="url(#artCyan)" stroke-width="2" stroke-linecap="round"/>
|
|
<path d="M 45 35 Q 15 65 -20 40" fill="none" stroke="url(#artPurple)" stroke-width="2" stroke-linecap="round" stroke-dasharray="4,3"/>
|
|
'''),
|
|
|
|
'article_ai_agent.svg': ('智', '''
|
|
<circle cx="0" cy="0" r="100" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4"/>
|
|
<polygon points="0,-65 56,-32 56,32 0,65 -56,32 -56,-32" fill="none" stroke="#C4B5FD" stroke-width="1.2"/>
|
|
<circle cx="0" cy="0" r="28" fill="url(#artPurple)" filter="url(#artShadow)"/>
|
|
<circle cx="0" cy="0" r="16" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="0" r="7" fill="#FFFFFF"/>
|
|
<circle cx="0" cy="-65" r="4.5" fill="#2563EB"/>
|
|
<circle cx="56" cy="-32" r="4.5" fill="#06B6D4"/>
|
|
<circle cx="56" cy="32" r="4.5" fill="#7C3AED"/>
|
|
<circle cx="0" cy="65" r="4.5" fill="#2563EB"/>
|
|
<circle cx="-56" cy="32" r="4.5" fill="#06B6D4"/>
|
|
<circle cx="-56" cy="-32" r="4.5" fill="#7C3AED"/>
|
|
'''),
|
|
|
|
'article_pos_offline.svg': ('核', '''
|
|
<circle cx="0" cy="0" r="115" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
|
|
<ellipse cx="-55" cy="0" rx="55" ry="50" fill="none" stroke="#CBD5E1" stroke-width="1.2"/>
|
|
<ellipse cx="55" cy="0" rx="55" ry="50" fill="none" stroke="#CBD5E1" stroke-width="1.2"/>
|
|
<circle cx="-55" cy="0" r="26" fill="url(#artBlue)" filter="url(#artShadow)"/>
|
|
<circle cx="-55" cy="0" r="10" fill="#FFFFFF"/>
|
|
<circle cx="55" cy="0" r="26" fill="url(#artCyan)" filter="url(#artShadow)"/>
|
|
<circle cx="55" cy="0" r="10" fill="#FFFFFF"/>
|
|
<path d="M -30 -10 C -10 -25 10 -25 30 -10" fill="none" stroke="url(#artBlue)" stroke-width="2" stroke-linecap="round"/>
|
|
<path d="M 30 10 C 10 25 -10 25 -30 10" fill="none" stroke="url(#artCyan)" stroke-width="2" stroke-linecap="round"/>
|
|
'''),
|
|
|
|
'article_erp_bom.svg': ('构', '''
|
|
<circle cx="0" cy="0" r="110" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4"/>
|
|
<line x1="-90" y1="0" x2="90" y2="0" stroke="#CBD5E1" stroke-width="1"/>
|
|
<line x1="0" y1="-90" x2="0" y2="90" stroke="#CBD5E1" stroke-width="1"/>
|
|
<g filter="url(#artShadow)">
|
|
<polygon points="0,-35 32,-12 32,22 0,42 -32,22 -32,-12" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.2"/>
|
|
<circle cx="0" cy="5" r="16" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="5" r="7" fill="#FFFFFF"/>
|
|
</g>
|
|
<circle cx="0" cy="-80" r="4.5" fill="#2563EB"/>
|
|
<circle cx="80" cy="0" r="4.5" fill="#06B6D4"/>
|
|
<circle cx="0" cy="80" r="4.5" fill="#7C3AED"/>
|
|
<circle cx="-80" cy="0" r="4.5" fill="#2563EB"/>
|
|
'''),
|
|
|
|
'article_security_whitepaper.svg': ('固', '''
|
|
<path d="M 0 -85 L 60 -50 V 0 C 60 50 0 85 0 85 C 0 85 -60 50 -60 0 V -50 Z" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.5" filter="url(#artShadow)"/>
|
|
<circle cx="0" cy="0" r="26" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="0" r="12" fill="#FFFFFF"/>
|
|
<circle cx="0" cy="0" r="5" fill="#1D4ED8"/>
|
|
'''),
|
|
|
|
'article_southwest_digital.svg': ('融', '''
|
|
<ellipse cx="0" cy="40" rx="180" ry="70" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
|
|
<path d="M -120 40 Q -60 -40 0 20 T 120 10" fill="none" stroke="url(#artBlue)" stroke-width="2.5" stroke-linecap="round"/>
|
|
<circle cx="-60" cy="-10" r="6" fill="#2563EB" filter="url(#artGlow)"/>
|
|
<circle cx="0" cy="20" r="7" fill="#06B6D4" filter="url(#artGlow)"/>
|
|
<circle cx="60" cy="-15" r="6" fill="#7C3AED" filter="url(#artGlow)"/>
|
|
'''),
|
|
|
|
'article_tdd_standards.svg': ('规', '''
|
|
<rect x="-85" y="-85" width="170" height="170" rx="6" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
|
|
<circle cx="0" cy="0" r="65" fill="none" stroke="#CBD5E1" stroke-width="1"/>
|
|
<polygon points="0,-40 38,25 -38,25" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.5" filter="url(#artShadow)"/>
|
|
<circle cx="0" cy="5" r="14" fill="url(#artBlue)"/>
|
|
<circle cx="0" cy="5" r="6" fill="#FFFFFF"/>
|
|
'''),
|
|
}
|
|
|
|
def generate_all():
|
|
for filename, (seal_char, elements) in articles.items():
|
|
svg_content = create_base_svg(seal_char, elements)
|
|
filepath = os.path.join(TARGET_DIR, filename)
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
f.write(svg_content.strip())
|
|
print(f"Generated concise article cover: {filename} ({len(svg_content)} bytes)")
|
|
|
|
if __name__ == '__main__':
|
|
generate_all()
|