很多个人网站一开始都在介绍“我是谁”,真正的文章反而被放在第二屏。墨痕的第一轮重构先做了一个产品决定:访问者打开网站时,最先看到的应该是内容,而不是作者简历。

这篇文章记录项目从目标、技术选型到服务拆分的过程。它不是一份框架功能清单,而是解释每个选择解决了什么问题。

先写清楚产品边界

墨痕需要完成四件事:

  1. 用安静、内容优先的页面发布 Markdown 技术文章。
  2. 通过单管理员后台管理文章、模型、助手角色和知识库。
  3. 让访客随时打开 AI 助手,并基于博客知识进行问答。
  4. 展示可以安全执行的 Python/LangChain 教学实验。

同时也主动排除了一些功能:不做用户注册、评论、点赞、多租户权限和通用在线 IDE。明确边界非常重要,因为个人项目最容易在“什么都想做”中失去主线。

为什么前端选择 Next.js

博客需要服务端渲染、动态 Sitemap、文章详情和管理端交互。Next.js App Router 能把公开页面、管理页面和 API 代理放在同一个 Web 应用中:

app/
├─ page.tsx                 # 首页文章时间线
├─ posts/[slug]/page.tsx    # 文章详情
├─ experiments/             # 代码实验
├─ admin/                   # 管理端
└─ api/                     # 到 FastAPI 的服务端代理

浏览器不会直接访问 Python API。Next.js Route Handler 在服务端转发请求,减少公开端口,也避免把内部地址暴露给客户端。

为什么把 AI 能力放在 Python

内容页面和 Agent 的变化节奏并不相同。Python 生态更适合 LangChain、文本解析、向量模型和 Qdrant,因此项目没有强行把全部逻辑塞进 Next.js,而是使用 FastAPI 作为业务与 Agent 服务。

flowchart LR
    B[浏览器] --> W[Next.js]
    W --> A[FastAPI]
    A --> M[(MySQL)]
    A --> Q[(Qdrant)]
    K[Worker] --> M
    K --> Q
    A --> L[Lab Runner]

这不是传统意义上庞大的微服务架构。六个容器运行在一台服务器上,拆分只是为了获得清晰的职责和安全边界。

MySQL 是唯一业务数据源

文章、模型配置、知识文档、分块、任务和用量都保存在 MySQL。Qdrant 只保存向量索引,原因有两个:

  • 业务状态需要事务、约束和可审计的数据结构。
  • 向量索引应该可以从原始文档重新生成,而不是成为无法恢复的唯一数据。

例如知识文档上传后,API 只负责保存原文并创建任务:

document.status = "pending"
db.add(IndexJob(document_id=document.id, action="index", status="pending"))
await db.commit()

独立 Worker 再消费任务、生成分块和向量。这样上传请求不会因为嵌入模型较慢而长时间阻塞。

为什么没有 Redis

在 2 核 4G 的个人服务器上,每增加一个基础设施都会增加内存与维护成本。当前任务量很小,MySQL 任务表已经能够承担知识索引和定时发布,因此首版没有增加 Redis、Celery 或消息队列。

这项选择不是说 Redis 不好,而是让复杂度与实际规模匹配。如果未来出现多个 Worker、任务优先级或大量并发,再引入消息队列会更合理。

从单容器到 Compose

最终生产环境包含:

服务职责
Web页面、管理端与服务端代理
Agent API业务 API、认证、对话与用量
Worker定时发布和知识索引
MySQL业务数据
Qdrant向量索引
Lab Runner受限代码执行

只有 Web 绑定 127.0.0.1,再由 Nginx 提供 80/443。其他服务只加入 Docker 私有网络。

web:
  ports:
    - "127.0.0.1:10001:10000"
  depends_on:
    agent-api:
      condition: service_healthy

这个细节比“容器都启动了”更重要:数据库和 Agent API 根本不应该直接暴露到公网。

实际开发顺序

项目不是一次写完的,而是按可验证的垂直切片推进:

  1. 先完成文章首页和详情页。
  2. 加入管理端与 MySQL,替换本地 MDX 作为业务数据源。
  3. 接入可配置模型和流式对话。
  4. 增加知识库、Worker 和混合检索。
  5. 增加用量记录与仪表盘。
  6. 最后实现隔离代码实验和生产部署。

每个阶段都必须能从界面走通,而不是只完成底层类和接口。这样的顺序让问题更早暴露,例如中文 slug 重复编码、异步 ORM 懒加载和 SSE 中断处理,都在真实页面流程中被发现。

阶段验收

  • 首页严格按发布时间展示文章,推荐内容不打乱时间线。
  • 后台新增草稿后可预览和发布。
  • 浏览器只能通过 Next.js 访问业务 API。
  • MySQL 数据和 Qdrant 索引都能备份和恢复。
  • 没有 API Key、数据库密码或 SSH 私钥进入 Git。

下一篇会进入管理端,看看 Markdown 写作、模型密钥和动态仪表盘是怎样连接起来的。