DESIGN.md 12 KB

基于 Flask 的个人博客系统设计文档

1. 概述

本系统是一个轻量级的个人博客内容管理方案,支持通过 Web 界面上传 Markdown 博文及关联图片。后端基于 Flask 完成 Markdown 到 HTML 的渲染(含 LaTeX 支持),生成静态页面并保存。不需要数据库,所有数据以文件系统方式组织。前端采用瀑布流布局展示文章列表,风格简洁美观,仿照朋友圈的卡片式设计。

2. 技术架构

  • 后端框架:Flask
  • Markdown 渲染markdown + 扩展(codehilite, fenced_code, tables 等)
  • LaTeX 渲染:前端 MathJax 3(服务端保留 $...$ / $$...$$ 标记)
  • 文件存储:按文章 ID 分目录存储原始 Markdown、图片,另生成独立静态 HTML 文件
  • 元数据管理:单一 posts_index.json 文件存储所有文章索引(无数据库)
  • 前端布局:CSS Grid 实现瀑布流卡片,深色/浅色干净主题,响应式设计

3. 目录结构

.
├── app.py                     # Flask 主程序,路由与核心逻辑
├── config.py                  # 配置文件(上传限制、路径等)
├── requirements.txt           # Python 依赖列表
├── templates/                 # Jinja2 模板
│   ├── base.html              # 全局基础模板(含 meta、MathJax、导航栏)
│   ├── index.html             # 博客首页(瀑布流文章列表)
│   ├── upload.html            # 上传表单页面
│   └── admin.html             # 管理页面(展示所有文章,提供删除操作)
├── static/                    # 静态资源(Flask 自动提供)
│   ├── css/
│   │   └── style.css          # 主样式表(瀑布流、卡片、暗色/亮色风格)
│   ├── js/
│   │   └── main.js            # 可选前端交互(如瀑布流优化、懒加载)
│   └── uploads/               # 用户上传的图片存储目录
│       └── posts/             # 每篇文章的图片子目录(详见下文)
├── generated/                 # 生成的静态 HTML 文件
│   └── <post_id>.html         # 每篇文章对应的独立页面
├── posts_data/                # 原始数据存储
│   └── <post_id>/             # 以文章 ID 命名的目录
│       ├── content.md         # 原始 Markdown 文件
│       └── images/            # 该文章用到的图片(软链接或实际存储)
│           └── 图片文件...
└── posts_index.json           # 全局索引文件,存储所有文章元数据

说明

  • 图片实际保存到 static/uploads/posts/<post_id>/ 下,以便通过 /static/uploads/posts/<post_id>/image.png 直接访问。
  • posts_data/<post_id>/images/ 可以是一个目录软链接,也可以在主程序中统一按 ID 查找图片路径。为简化,可以在上传时直接将图片保存到 static/uploads/posts/<post_id>/,而 posts_data 中只保留 Markdown 副本。两种方案均可,本设计采用统一保存到 static/uploads/posts/<post_id>/,在 posts_data 中不再重复存储图片。

4. 需要创建的文件清单

文件路径 作用
app.py Flask 应用入口,定义所有路由(首页、上传、文章详情、管理、删除)
config.py 配置项:UPLOAD_FOLDER, GENERATED_FOLDER, INDEX_FILE, ALLOWED_EXTENSIONS, MAX_CONTENT_LENGTH
requirements.txt 依赖列表(Flask, markdown, Pygments 等)
templates/base.html 全局模板,包含 HTML 骨架、导航栏、MathJax 脚本、基础 CSS/JS 引用
templates/index.html 首页,继承 base.html,使用瀑布流卡片渲染文章列表
templates/upload.html 上传表单,支持 Markdown 文件 + 多图片选择
templates/admin.html 后台管理,以表格形式列出所有文章,提供删除按钮
static/css/style.css 完整样式定义(瀑布流栅格、卡片样式、按钮、代码高亮等)
static/js/main.js 可选前端增强(如卡片悬停效果、动态瀑布流重排)
posts_index.json 运行时自动生成/更新,存储结构见下文

5. 核心功能设计

5.1 文章上传与处理流程

  1. 接收请求:POST /upload,接收 title(字符串),markdown_file(文件),images(多文件列表)。
  2. 创建文章 ID:使用时间戳(如 int(time.time()*1000))或 uuid4 生成唯一 post_id
  3. 创建目录
    • 图片保存目录:static/uploads/posts/<post_id>/
    • 原始 Markdown 保存目录:posts_data/<post_id>/
  4. 保存文件
    • 将 Markdown 文件保存为 posts_data/<post_id>/content.md
    • 将每个图片保存到 static/uploads/posts/<post_id>/ 下,保留原文件名(需过滤危险字符)。
  5. 解析 Markdown
    • 读取 content.md 内容,提取 Front Matter(若存在,使用 frontmatter 库或正则),获得 title, date, summary 等。若 Front Matter 缺失,则使用表单中的 title,当前时间作为发布日期,摘要从正文前 200 字生成。
    • 处理图片 URL:扫描 Markdown 中所有 ![](filename) 式的本地图片引用,将 filename 替换为 /static/uploads/posts/<post_id>/filename。若图片未上传,记录警告但继续渲染。
  6. 渲染 HTML
    • 使用 markdown.markdown() 并将扩展启用:extra, codehilite, tables, fenced_code
    • 若需 LaTeX 支持,在渲染结果中保留 $...$$$...$$(不进行转义)。
  7. 生成静态页面
    • 使用 Jinja2 模板(如 post_template.html,可从 base.html 继承)渲染完整 HTML,内容区为上述生成的 HTML,标题为文章标题。
    • 保存到 generated/<post_id>.html
  8. 更新索引
    • 读取 posts_index.json,新增一条记录:{ "id": post_id, "title": title, "date": iso_date, "summary": summary, "thumbnail": 首张图片路径或空 }
    • 按日期倒序排序后写回文件。

5.2 列表页渲染(瀑布流)

  • 路由 / 读取 posts_index.json,将文章列表按日期倒序传入模板。
  • 前端使用 CSS Grid 布局实现瀑布流:

    .waterfall {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1.5rem;
    }
    .card {
    background: white;
    border-radius: 20px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.05);
    overflow: hidden;
    transition: transform 0.2s;
    }
    .card:hover { transform: translateY(-4px); }
    
  • 每个卡片包含:文章标题、日期、摘要、若存在缩略图则显示在卡片顶部。缩略图可从索引的 thumbnail 字段获取。

5.3 文章详情页

  • 路由 /post/<post_id> 使用 send_from_directory 返回 generated 目录下的 <post_id>.html
  • 若文件不存在,返回 404 页面(可自定义模板)。
  • 生成的静态页面中已包含 MathJax 脚本,正文中的 LaTeX 会被自动渲染。

5.4 管理功能

  • 管理页面GET /admin,读取索引展示所有文章,提供删除按钮(需注意 CSRF,可简单用 POST 表单)。
  • 删除文章POST /admin/delete/<post_id>,执行以下操作:
    • 删除 generated/<post_id>.html
    • 删除 posts_data/<post_id>/ 整个目录
    • 删除 static/uploads/posts/<post_id>/ 整个目录
    • posts_index.json 中移除该条目并保存
    • 重定向回 /admin

6. 数据格式设计

6.1 posts_index.json 结构

[
  {
    "id": "1703123456789",
    "title": "Python 中的装饰器",
    "date": "2023-12-21T10:30:00",
    "summary": "装饰器是 Python 一个强大的功能...",
    "thumbnail": "/static/uploads/posts/1703123456789/code.png"
  },
  ...
]

6.2 Markdown Front Matter 建议(可选)

若用户希望在 Markdown 文件头部定义元数据,支持以下格式:

---
title: 文章标题
date: 2024-01-01
summary: 自定义摘要
---

可使用 python-frontmatter 库解析,若未安装则回退到正则或忽略。在本设计中推荐使用,但非强制。

7. 关键实现细节

7.1 图片路径修正函数

在渲染 Markdown 之前,预处理文本:

def fix_image_paths(md_content: str, post_id: str) -> str:
    pattern = r'!\[([^\]]*)\]\(([^)]+)\)'
    def repl(match):
        alt, src = match.groups()
        # 仅当 src 不含 '/' 或 'http' 且扩展名为图片时视为本地文件
        if not src.startswith(('http://', 'https://', '/', '#')) and '.' in src:
            new_src = f"/static/uploads/posts/{post_id}/{src}"
            return f'![{alt}]({new_src})'
        return match.group(0)
    return re.sub(pattern, repl, md_content)

7.2 Markdown 渲染配置

import markdown
md = markdown.Markdown(extensions=[
    'extra',
    'codehilite',
    'tables',
    'fenced_code'
])
html_body = md.convert(fixed_md)

7.3 摘要提取(无 Front Matter 时)

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_body, 'html.parser')
text = soup.get_text()
summary = text[:200] + ('...' if len(text) > 200 else '')

7.4 缩略图提取

优先使用 Front Matter 中指定的 thumbnail,否则取正文中第一张图片的 URL。

8. 前端样式设计要点

8.1 整体风格

  • 背景色:#f8fafc(浅灰色),卡片纯白 #ffffff,圆角大半径(20px)。
  • 字体:系统默认无衬线(-apple-system, BlinkMacSystemFont, "Segoe UI")。
  • 导航栏:半透明白色毛玻璃效果,包含 “博客”、“上传”、“管理” 链接。
  • 响应式:移动端禁用悬停效果,栅格 minmax(250px,1fr)

8.2 卡片内容

  • 若缩略图存在:<img> 宽度 100%,高度 180px,object-fit: cover。
  • 标题:粗体,大小 1.25rem,上下边距。
  • 日期:小字,灰色。
  • 摘要:浅灰色文字,行高 1.5。
  • 底部可加 “阅读全文 →” 链接,指向 /post/<id>

8.3 代码高亮

使用 Pygments 生成 CSS 类,在 style.css 中引入一个默认高亮主题(如 monokaifriendly)。需在 Markdown 渲染时启用 codehilite 扩展并生成对应 HTML。

8.4 MathJax 配置

base.html 中添加:

<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script" async></script>

并设置 tex: { inlineMath: [['$', '$'], ['\\(', '\\)']] }

9. 路由概览

路径 方法 说明
/ GET 首页,瀑布流文章列表
/upload GET 显示上传表单
/upload POST 处理上传,保存文章并生成静态页
/post/<post_id> GET 查看静态文章详情
/admin GET 管理页面,列出所有文章
/admin/delete/<post_id> POST 删除文章及相关文件
/static/<path:filename> GET Flask 默认静态文件路由(用于 CSS、JS、图片)

10. 部署与运行说明

  1. 安装依赖:pip install -r requirements.txt
  2. 运行:python app.py(默认监听 0.0.0.0:5000
  3. 使用生产服务器时,可将 generated/ 目录通过 Nginx 直接托管提升性能,但 Flask 本身也可完成。

11. 扩展建议

  • 支持 RSS 订阅:从索引生成 XML。
  • 添加文章标签系统:在索引中增加 tags 列表,前端做筛选。
  • 缓存优化:列表页可定期重新生成静态文件。
  • 图片压缩/缩略图生成:使用 Pillow 在上传时生成不同尺寸。

本设计文档已覆盖完整架构、目录结构、核心代码逻辑及样式取向。请后续根据本设计实现具体代码。