|
@@ -0,0 +1,260 @@
|
|
|
|
|
+# 基于 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` 替换为 `/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 布局实现瀑布流:
|
|
|
|
|
+ ```css
|
|
|
|
|
+ .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` 结构
|
|
|
|
|
+
|
|
|
|
|
+```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 文件头部定义元数据,支持以下格式:
|
|
|
|
|
+
|
|
|
|
|
+```yaml
|
|
|
|
|
+---
|
|
|
|
|
+title: 文章标题
|
|
|
|
|
+date: 2024-01-01
|
|
|
|
|
+summary: 自定义摘要
|
|
|
|
|
+---
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+可使用 `python-frontmatter` 库解析,若未安装则回退到正则或忽略。在本设计中推荐使用,但非强制。
|
|
|
|
|
+
|
|
|
|
|
+## 7. 关键实现细节
|
|
|
|
|
+
|
|
|
|
|
+### 7.1 图片路径修正函数
|
|
|
|
|
+
|
|
|
|
|
+在渲染 Markdown 之前,预处理文本:
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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''
|
|
|
|
|
+ return match.group(0)
|
|
|
|
|
+ return re.sub(pattern, repl, md_content)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 7.2 Markdown 渲染配置
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+import markdown
|
|
|
|
|
+md = markdown.Markdown(extensions=[
|
|
|
|
|
+ 'extra',
|
|
|
|
|
+ 'codehilite',
|
|
|
|
|
+ 'tables',
|
|
|
|
|
+ 'fenced_code'
|
|
|
|
|
+])
|
|
|
|
|
+html_body = md.convert(fixed_md)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 7.3 摘要提取(无 Front Matter 时)
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+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` 中引入一个默认高亮主题(如 `monokai` 或 `friendly`)。需在 Markdown 渲染时启用 `codehilite` 扩展并生成对应 HTML。
|
|
|
|
|
+
|
|
|
|
|
+### 8.4 MathJax 配置
|
|
|
|
|
+
|
|
|
|
|
+在 `base.html` 中添加:
|
|
|
|
|
+
|
|
|
|
|
+```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 在上传时生成不同尺寸。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+> 本设计文档已覆盖完整架构、目录结构、核心代码逻辑及样式取向。请后续根据本设计实现具体代码。
|