项目名称 / Project Name
Personal Blog — 全栈个人博客系统 项目Wiki
简介 / Summary
一个集博客写作、AI 聊天助手、RAG 知识库、实时金融看板、多源信息观测站于一体的全栈个人知识管理平台。
技术栈 / Tech Stack
- FastAPI
- SQLAlchemy
- Jinja2
- Alpine.js
- ChromaDB
- SQLite
- Redis
链接 / Links
详细介绍 / Details
一、项目介绍
1.1 项目定位
Personal Blog 是一个面向技术从业者的全栈个人博客系统,定位为"个人数字基地"——既是输出内容(博客文章)的平台,也是输入信息(资讯聚合、AI 问答)的枢纽。它不是一个简单的博客框架,而是一个集成了多种工具的个人工作台。
1.2 核心理念
- All-in-One — 博客写作 + AI 助手 + 金融看板 + 资讯聚合,一站式满足日常需求
- 服务端渲染优先 — 页面首屏快速加载,前端 Alpine.js 做交互增强(非 SPA)
- 开箱即用 — SQLite + 本地嵌入模型,无需外部服务即可运行核心功能
- 渐进增强 — Redis 可选、LLM 可切换、嵌入模型本地运行,组件解耦可独立升级
1.3 适用场景
- 个人技术博客写作与发布
- AI 面试辅导(RAG 增强的知识问答)
- 金融市场追踪(黄金、A 股指数)
- 多源信息聚合(ArXiv、GitHub Trending、知乎热榜等)
- 个人知识库管理
二、功能总览
2.1 功能矩阵
| 功能模块 | 子功能 | 说明 |
|---|---|---|
| 博客系统 | 文章 CRUD | Markdown 写作,服务端渲染为 HTML |
| 分类/标签 | 双维度内容组织 | |
| Markdown 批量导入 | 解析 YAML frontmatter,自动建标签、分类 | |
| RSS / Sitemap | 订阅与 SEO | |
| 访问统计 | PV/UV 按日追踪 | |
| AI 聊天 | 多会话管理 | 创建、重命名、删除对话 |
| SSE 流式响应 | 逐 token 推送到前端,打字机效果 | |
| RAG 检索增强 | 从本地知识库检索后由 LLM 回答 | |
| 多 LLM 支持 | DeepSeek / OpenAI / Ollama / 自定义 | |
| 面试助手 | 角色随机出题 | 5 个技术角色 |
| 答案自动评估 | LLM 评分与参考答案 | |
| 错题本 | 自动记录错误,支持复习 | |
| 实时金融 | 黄金价格 | 多 API 源 + 每 30 秒刷新 + Redis 缓存 |
| A 股指数 | 新浪财经接口 + MA/RSI 技术指标 | |
| ECharts 图表 | 历史价格走势折线图 | |
| 自选股管理 | 数据库持久化 | |
| 世界观测站 | 信息聚合仪表板 | AI 前沿、GitHub 热榜、实时热点 |
| 多源采集 | RSS / JSON API / 爬虫 三种策略 | |
| LLM 摘要 | AI 资讯一键自动总结 | |
| 逐级配置 | 栏目 → 信源 → 开关 + 数量上限 | |
| 管理后台 | 概览面板 | 统计卡片 + ECharts 趋势图 |
| 页面内容管理 | 首页/观测站/项目三个页面的区块编辑器 | |
| 站点设置 | Hero 文案、联系方式、价值观等 |
2.2 页面清单
| 路径 | 页面 | 功能 |
|---|---|---|
/ |
首页 | Hero 区 + 精选项目 + 最新文章 + 市场快照 + 新闻速览 |
/blog |
博客列表 | 分类导航 + 文章列表 + 标签云 + 日历 |
/article/{id} |
文章详情 | 正文 + 目录 + 相关文章 + 评论 |
/world |
观测站 | 区块信息聚合(精选/前沿/热点/引语) |
/finance |
金融看板 | ECharts 黄金 + 股票图表 |
/projects |
项目列表 | 项目卡片网格 |
/chat |
AI 聊天 | 侧栏会话列表 + 主聊天区,SSE 流式 |
/dashboard |
管理后台 | 侧栏导航 + 多面板 SPA |
/tools |
工具页 | 工具索引 |
三、技术栈
3.1 技术选型
| 层次 | 选型 | 理由 |
|---|---|---|
| Web 框架 | FastAPI 0.115 | 异步原生、性能好、自动 OpenAPI 文档 |
| ASGI 服务 | Uvicorn 0.34 | FastAPI 官方推荐,支持热重载 |
| ORM | SQLAlchemy 2.0 | 成熟稳定,迁移方便 |
| 数据库 | SQLite(默认) | 零配置,适合个人博客;可切换 PostgreSQL |
| 模板引擎 | Jinja2 | FastAPI 内置,语法强大 |
| 前端交互 | Alpine.js | 轻量(15KB),声明式,渐进增强 |
| 数据可视化 | ECharts 5.5 | 功能强大,中文文档好 |
| LLM 客户端 | OpenAI SDK 1.30+ | 兼容 DeepSeek/Ollama 等 |
| 向量数据库 | ChromaDB 0.5+ | 嵌入式,零依赖,支持持久化 |
| 嵌入模型 | BAAI/bge-small-zh | 中文效果好,轻量(33MB) |
| 缓存 | Redis 7(可选) | 自动降级到内存字典 |
| 定时任务 | APScheduler 3.10 | 进程内调度,无需外部依赖 |
| 部署 | Docker Compose | 一键部署,依赖管理 |
3.2 架构设计亮点
SSR + Alpine.js 混合模式: 项目有意避免了全 SPA 方案,采用服务端渲染为主、前端增强为辅的架构。页面首屏 HTML 由 Jinja2 在服务端填充数据生成,直接返回完整内容。Alpine.js 仅对需要交互的部分(聊天 SSE、管理后台编辑器、观测站配置面板)发挥作用。好处是首屏快、SEO 好、不需要前端构建工具链。
自动降级设计: 项目多处实现了自动降级,保证核心功能不依赖外部服务:
Redis 不可用 → 自动降级为内存字典缓存(无感)
LLM API 失败 → 聊天功能报错,博客核心不受影响
黄金/股票源超时 → 自动切换到备用 API
嵌入模型加载失败 → RAG 降级为纯 LLM 问答
四、系统架构
4.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ 浏览器端 │
│ Jinja2 HTML (服务端渲染) + Alpine.js (交互增强) │
│ ECharts (图表) + Font Awesome (图标) │
└──────────────────────────┬──────────────────────────────────┘
│ HTTP / SSE
▼
┌─────────────────────────────────────────────────────────────┐
│ FastAPI 应用层 │
│ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────────┐ │
│ │ pages │ │ api / │ │ chat │ │ admin / │ │
│ │ (页面路由)│ │ market │ │ (SSE流)│ │ admin_page │ │
│ └────┬────┘ └────┬─────┘ └────┬───┘ └──────┬───────┘ │
│ │ │ │ │ │
│ ┌────▼─────────────▼─────────────▼──────────────▼───────┐ │
│ │ 服务层 / 核心层 │ │
│ │ agent.py │ md_importer.py │ auth.py │ i18n.py │ │
│ │ cache.py │ page_manager │ config.py │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌────────────────────┐ ┌──────────────────────────────┐ │
│ │ RAG 引擎 │ │ 定时任务调度器 │ │
│ │ ingestion.py │ │ gold_service.py │ │
│ │ embedding.py │ │ stock_service.py │ │
│ │ vector_store.py │ │ news_service.py │ │
│ │ retriever.py │ │ scheduler.py │ │
│ └────────┬───────────┘ └────────┬─────────────────────┘ │
└───────────┼───────────────────────┼──────────────────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌──────────────────────────────┐
│ 数据持久化 │ │ 外部服务 │
│ SQLite (blog.db) │ │ DeepSeek / OpenAI / Ollama │
│ ChromaDB (向量库) │ │ 新浪财经 API │
│ Redis (缓存,可选) │ │ gold-api.com │
│ /data/chroma/ │ │ RSS 订阅源 × 14+ │
└────────────────────┘ └──────────────────────────────┘
4.2 请求流转
公开 HTML 页面:Jinja2 模板渲染 → 返回完整 HTML
JSON API: 路由处理 → 数据库查询 → JSON 响应
SSE 流式: 创建会话 → RAG 检索 → LLM 流式生成 → SSE 推送到前端
页面区块: Alpine fetch → JSON → 前端渲染
五、模块详解
5.1 博客系统
文章模型采用扁平分类 + 多对多标签的组织方式。分类是文章上的字符串字段,存储在 SiteSetting.blog_categories(JSON 数组)中;标签是独立表,通过关联表实现 M2M。分类数量少且稳定(<20),适合粗粒度分组;标签数量不限,适合细粒度标记。
Markdown 导入引擎(md_importer.py)解析 YAML frontmatter:
- 用 --- 分界解析 frontmatter
- 无 frontmatter 时从 # 标题推导 title
- 标题去重检测,标签自动创建
- 支持单文件上传 / 目录批量导入
渲染管线: 编辑器预览和详情页渲染使用同一份 Python 代码(markdown 库 + fenced_code/tables/toc/nl2br 扩展),保证所见即所得。详情页额外处理阅读时间估算(中英文混合计数)和相关文章推荐(标签匹配度排序)。
访问统计: PageVisit 表按 (date, section, path) 记录访问,ip_hash 用于 UV 去重。PV = 所有记录求和,UV = 按日期+ip_hash 去重计数。
5.2 AI 聊天 & RAG
整体流程:
用户输入 → 创建/选择会话 → RAG 检索 → 构造 Prompt → LLM 流式生成 → 保存消息
RAG 检索管线(4 个文件,职责清晰):
ingestion.py — 智能分块,按 ### / #### / 段落 三级策略递进。每个 chunk 携带 source_file、topic、section 等元数据。
embedding.py — 惰性加载,首次调用时才下载/加载 BAAI/bge-small-zh 模型(33MB),CPU 推理一次不到 100ms。
vector_store.py — ChromaDB 封装,HNSW 索引 + 余弦距离,支持按 metadata 过滤。
retriever.py — 检索结果格式化为带编号引用的 LLM 上下文,同时提取 sources 元数据供前端展示。
SSE 流式响应: 聊天 API 使用 Server-Sent Events 实现打字机效果,前端使用 EventSource 或 fetch + ReadableStream 消费。
面试助手: 聊天功能的垂直应用,在 RAG 知识库上加了一层角色管理。5 个预置角色(前端/后端/算法/AI/数据),每个角色有独立的问题生成提示词和评估标准。错题本(WrongAnswer)和面试笔记(InterviewNote)支持复习。
LLM 客户端: 使用 OpenAI SDK 的统一接口,通过修改 base_url 和 model 参数即可切换 Provider:
config = {
"deepseek": {"base_url": "https://api.deepseek.com", "model": "deepseek-chat"},
"openai": {"base_url": "https://api.openai.com/v1", "model": "gpt-4o"},
"ollama": {"base_url": "http://localhost:11434/v1", "model": "qwen2.5:7b"},
}
5.3 实时金融
定时任务架构(APScheduler):
黄金: 每 30 秒 → gold-api.com/metals.live → Redis(30s TTL)
股票: 每 30 秒 → 新浪财经 API → Redis(60s TTL) → MA/RSI 计算
新闻: 每小时 → 14+ RSS/API 源 → LLM 摘要 → 分类存储
多源降级: 黄金服务配置了多个数据源,按优先级依次尝试,第一个成功的数据源即返回,全部失败则返回空值,保证服务不因单个 API 不可用而崩溃。
技术指标: 股票服务自动计算 MA5/10/20/60 移动均线和 RSI14 相对强弱指数。
新闻聚合: 支持 3 种采集策略(RSS / JSON API / 自定义爬虫),覆盖 14+ 信源。通过 LLM 为每条新闻生成一句话摘要,关键词匹配分类。内置健康状态追踪,可在管理后台查看每个信源的采集成功率。
5.4 世界观测站
观测站是信息聚合仪表板,管理后台的逐级配置体系是其核心设计:
栏目 (block) 级别: max_items(总上限) + per_source_limit(单信源上限)
└─ 信源 (source) 级别: max_items(贡献上限) + enabled(启停开关)
观测站的内容遵循"人工编辑优先"的同步策略:自动采集数据不会覆盖管理员手动添加的精选条目。
5.5 管理后台
页面内容管理框架(PageManager): 一个全局单例的区块注册系统,实现了页面内容的可配置化。注册区块后,管理后台自动生成编辑器,数据保存到 SiteSetting 表(万能键值表),前台通过 API 读取展示。
已有的区块注册(16 个区块横跨 4 个页面):
| 页面 | 区块 | 用途 |
|---|---|---|
| world | slogan / weekly_picks / quotes | 观测站标语、精选、引语 |
| home | hero_text / contacts / market_config / featured_projects | 首页内容 |
| blog | blog_intro | 博客页简介 |
| projects | project_list | 项目列表 |
管理后台认证采用三态逻辑:开发模式自动授权(ADMIN_BY_DEFAULT=true),未配置凭据时禁止登录,Session 认证。前端通过 base.html 中的 is_admin 变量控制管理入口的可见性。
管理后台是一个 Alpine.js 驱动的 SPA,侧栏导航切换不同的面板视图(x-show="tab==='xxx'"),无路由切换。编辑区块内容时的粘底保存栏确保数据不会意外丢失。
5.6 缓存模块
cache.py 实现了 Redis 优先、内存降级的双模缓存:
# 核心设计:按需连接 Redis,连接失败时静默降级
class Cache:
def __init__(self):
self._redis = None
self._memory = {} # 降级方案
缓存使用分布:
| 数据 | TTL | 说明 |
|---|---|---|
| 黄金价格 | 30s | 高频刷新 |
| 黄金历史 | 无(列表追加) | 保留最近 500 条 |
| 股票行情 | 60s | 低频刷新 |
| AI 资讯 | 2h | 长时间有效 |
| 页面区块 | 不缓存 | 数据库直读 |
5.7 国际化
200+ 翻译 key,覆盖导航、编辑器、博客、观测站、管理后台、评论等功能。语言选择通过 URL 参数 ?lang=en 切换,存储在 Cookie 中持久化。
# 轻量级内联翻译,无外部 i18n 框架
TRANSLATIONS = {
"zh": { "nav_home": "首页", "write_article": "写文章", ... },
"en": { "nav_home": "Home", "write_article": "Write", ... },
}
六、数据模型
实体关系
Article ──→ article_tags ←── Tag ChatSession ──→ ChatMessage
│ SiteSetting (KV 存储)
├── Comment Watchlist / PriceAlert
└── PageVisit WrongAnswer / InterviewNote
核心表说明
articles(文章): title, content(Markdown), category(扁平分类), is_published, is_featured, view_count, tags(M2M)
site_settings(站点设置 — 万能键值表): key(唯一主键), value(JSON 字符串)。存储内容:hero 文案、分类列表、页面区块数据、观测站配置等。
page_visits(访问统计): date, section(板块), path, ip_hash(UV 去重)
chat_messages(聊天消息): session_id(FK), role(user/assistant), content, sources(JSON,RAG 来源)
七、部署运维
Docker Compose 部署
项目使用 Docker Compose 管理两个服务:blog(FastAPI + Uvicorn)和 redis(可选)。
services:
redis: # 可选,无 Redis 时自动降级
blog: # 核心服务,端口 8000
volumes:
- ./data:/app/data # ChromaDB 持久化
- ./blog.db:/app/blog.db # SQLite 数据库
环境变量
| 变量 | 说明 |
|---|---|
ADMIN_PASSWORD |
管理员密码(生产必须修改) |
SITE_URL |
站点 URL(影响 RSS/Sitemap) |
LLM_PROVIDER |
deepseek / openai / ollama / custom |
DEEPSEEK_API_KEY |
DeepSeek API 密钥 |
DATABASE_URL |
数据库连接(默认 SQLite,可切换 PostgreSQL) |
数据备份
需要备份的数据:
- blog.db — SQLite 数据库(核心数据)
- data/chroma/ — ChromaDB 向量数据
- app/static/uploads/ — 上传文件
- .env — 环境配置
八、踩坑与经验教训
前端类
Alpine.js 大括号不配对导致整个组件不初始化: 编辑观测站配置时,所有 Alpine.js 组件停止响应,x-show/x-model 全部失效。定位后发现是模板中某处 }} 多写了一个花括号,导致 Alpine 解析 template 语法时报错。Jinja2 的 {{ }} 和 Alpine.js 的 x-data 混用时尤其要注意大括号配对。
管理后台大模板的可维护性: dashboard.html 约 1800 行,是一个巨大的单文件 Alpine.js SPA。随着功能增加,文件越来越难维护。建议如果继续扩展,考虑拆分为多个模板片段用 {% include %} 组合。
CSS 缓存: 修改 style.css 后浏览器不更新。解决方案是在 base.html 中引用时带版本号 ?v=N。
后端类
SQLite 并发写冲突: 定时任务每 30 秒写一次数据库,与用户操作同时发生时可能出现 database is locked。解决方案:SQLAlchemy 连接池设置 check_same_thread=False,定时任务使用独立会话,缓存层减少落盘频率。
APScheduler + asyncio 混用: 定时任务使用 ThreadPoolExecutor 执行异步函数,但 asyncio 事件循环在另一个线程中。使用 asyncio.run() 包装异步函数解决。
自动采集数据覆盖人工编辑内容: 观测站的每周精选板块,管理员手动添加的内容在下一轮自动采集后被覆盖。后来引入"固化"(pinned)标记,人工编辑的内容标记为 pinned: true,自动更新时跳过。
RAG 文档块大小不合理: 初始设置最大 chunk 为 3000 字符,LLM 上下文窗口中有效信息密度低。将 max_chunk_chars 从 3000 调整到 1200,按 ### / #### / 段落 三级分块,保证每个 chunk 信息聚焦。
部署类
Docker 时区问题: 容器内默认 UTC 时区,导致定时任务和北京时间差 8 小时。在 Docker 和 docker-compose 中设置 TZ=Asia/Shanghai。
国内网络环境下的 Docker 构建: Dockerfile 中配置了清华/阿里云镜像源加速 apt 和 pip,HuggingFace 使用 hf-mirror.com 镜像站,Playwright Chromium 使用 npmmirror.com 镜像。
数据持久化挂载: 在 docker-compose.yml 中挂载 ./data 和 ./blog.db 保证容器重启后数据不丢失。
九、开发指南
搭建开发环境
cd personal_blog
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # 按需修改
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
添加新功能流程
- 数据层:在
models.py添加新模型 → 启动时自动建表 - 业务层:在
routes/下添加或扩展现有路由 - 展示层:在
templates/中创建新模板或修改现有模板 - 可配置内容:在
page_manager.py注册新区块 →dashboard.html添加编辑器面板
项目结构
personal_blog/
├── app/
│ ├── main.py # FastAPI 入口
│ ├── config.py # 配置加载
│ ├── database.py # 数据库 & 自动迁移
│ ├── models.py # 15 个 ORM 模型
│ ├── admin/page_manager # 页面区块框架
│ ├── agent/ # LLM 客户端
│ ├── rag/ # RAG 引擎
│ ├── routes/ # 9 个路由模块
│ ├── services/ # 4 个后台服务
│ ├── static/ # CSS / JS / 上传
│ └── templates/ # 15 个 Jinja2 模板
├── data/chroma/ # 向量数据库
└── Dockerfile / docker-compose.yml
附:版本历史
| 阶段 | 功能 | 说明 |
|---|---|---|
| v1.0 | 博客系统 | 文章 CRUD + 分类标签 + Markdown |
| v1.1 | 管理后台 | Alpine.js SPA 后台面板 |
| v1.2 | 国际化 | zh/en 双语支持 |
| v1.3 | AI 聊天 | LLM + SSE 流式 |
| v1.4 | RAG 知识库 | ChromaDB + BGE 嵌入 |
| v1.5 | 金融看板 | 黄金 + 股票 + ECharts |
| v1.6 | 面试助手 | 角色出题 + 自动评估 |
| v1.7 | 世界观测站 | 多源信息聚合 |
| v1.8 | 页面内容框架 | PageManager 区块系统 |
| v1.9 | Docker 部署 | 容器化 + 部署脚本 |
| v2.0 | 数据一致性重构 | Category 归一化 + 全项目多路径写入审计 + 防御体系文档 |
相关文档: 多路径写入一致性:从一次 Debug 到系统性防御 — 完整记录分类数据不一致问题的排查、修复与防御体系。
还没有评论,来第一个吧