Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

11 KiB
Raw Permalink Blame History

Personal Blog - 后端设计文档

FastAPI + SQLAlchemy + SQLite 架构


1. 应用入口 (app.py)

1.1 启动流程

1. 创建 FastAPI 实例,配置 CORS
2. 注册静态文件服务 (uploads/)
3. 包含所有路由模块
4. 启动时自动创建数据库表 (create_all)

1.2 CORS 配置

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],           # 开发环境允许全部
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

1.3 路由注册

路由前缀 文件 说明
/api/auth routers/auth.py 登录、Token、管理员创建
/api/posts routers/posts.py 文章 CRUD、搜索、统计
/api/categories routers/categories.py 分类 CRUD
/api/tags routers/tags.py 标签 CRUD
/api/images routers/images.py 图片上传/删除
/api/settings routers/settings.py 系统设置、自定义页面
/api/crawl routers/crawl.py 通用网页采集
/api/wechat-crawl routers/wechat_crawl.py 公众号文章采集
/uploads app.py 静态图片文件服务
/ app.py 静态文件 (index.html 等)

2. 数据库模型 (models.py)

2.1 ER 关系图

┌──────────────┐       ┌──────────────┐       ┌──────────────┐
│    Post      │       │   Category   │       │     Tag      │
├──────────────┤       ├──────────────┤       ├──────────────┤
│ id (PK)      │──────►│ id (PK)      │       │ id (PK)      │
│ title        │  N:1  │ name         │       │ name         │
│ slug (UQ)    │       │ description  │       └──────────────┘
│ content      │       └──────────────┘              ▲
│ summary      │                                     │
│ cover_image  │       ┌──────────────┐       ┌──────┴──────┐
│ is_published │       │  post_tags   │       │ post_tags   │
│ is_draft     │◄──────┤ post_id (FK) │       │ tag_id (FK) │
│ created_at   │  N:M  │ tag_id (FK)  │       └─────────────┘
│ updated_at   │       └──────────────┘
│ view_count   │
│ category_id  │       ┌──────────────┐
│ (FK)         │       │   Setting    │
└──────────────┘       ├──────────────┤
                       │ id (PK)      │
                       │ site_name    │
                       │ site_desc    │
                       │ ...          │
                       └──────────────┘

┌──────────────┐       ┌──────────────┐
│    Admin     │       │    Page      │
├──────────────┤       ├──────────────┤
│ id (PK)      │       │ id (PK)      │
│ username(UQ) │       │ title        │
│ password_hash│       │ slug (UQ)    │
│ created_at   │       │ content      │
└──────────────┘       │ is_nav       │
                       │ nav_order    │
                       └──────────────┘

2.2 模型详解

Post (文章)

字段 类型 说明
id Integer, PK 主键自增
title String(200) 文章标题
slug String(200), UQ URL 标识,如 hello-world
content Text Markdown 格式正文
summary Text 文章摘要
cover_image String(500) 封面图 URL(可为空)
is_published Boolean 是否已发布
is_draft Boolean 是否草稿
created_at DateTime 创建时间
updated_at DateTime 更新时间
view_count Integer 浏览次数
category_id Integer, FK 所属分类(可为空)
  • is_published 和 is_draft 互斥:发布后 is_published=True, is_draft=False
  • 状态组合:
    • 草稿:is_published=False, is_draft=True
    • 已发布:is_published=True, is_draft=False
    • 暂不支持 "已发布但隐藏"

Category (分类)

字段 类型 说明
id Integer, PK 主键自增
name String(50), UQ 分类名称
description Text 分类描述
created_at DateTime 创建时间
  • 删除分类时,关联文章自动设为 category_id=None

Tag (标签)

字段 类型 说明
id Integer, PK 主键自增
name String(30), UQ 标签名称
created_at DateTime 创建时间
  • 与 Post 为多对多关系,通过 post_tags 关联表实现
  • 删除标签时自动解除关联

Admin (管理员)

字段 类型 说明
id Integer, PK 主键自增
username String(50), UQ 用户名
password_hash String(255) bcrypt 哈希密码
created_at DateTime 创建时间
  • 系统初始化时创建默认管理员(admin / admin123)

Setting (系统设置)

字段 类型 说明
id Integer, PK 主键自增
site_name String(100) 站点名称
site_description Text 站点描述
site_logo String(500) Logo URL
posts_per_page Integer 每页文章数
theme String(50) 主题标识
custom_css Text 自定义 CSS
icp_number String(50) 备案号
copyright String(200) 版权信息
analytics_code Text 统计代码
  • 单记录表(get_or_create_settings 保证只有一条记录)

Page (自定义页面)

字段 类型 说明
id Integer, PK 主键自增
title String(200) 页面标题
slug String(200), UQ URL 标识
content Text Markdown 内容
is_nav Boolean 是否在导航栏显示
nav_order Integer 导航排序
created_at DateTime 创建时间
updated_at DateTime 更新时间
  • 限制最多 5 个自定义页面

3. 认证机制 (routers/auth.py)

3.1 JWT Token

  • 算法:HS256
  • 密钥:硬编码 your-secret-key-123(生产环境应改为环境变量)
  • 有效期:30 天
  • 签发内容:{"sub": "admin", "exp": timestamp}

3.2 双重认证方式

  1. Bearer Token(管理后台使用)

    Authorization: Bearer <token>
    
  2. Cookie Token(可选,用于同源场景)

    Cookie: access_token=<token>
    

3.3 鉴权装饰器

async def get_current_admin(
    token: str = Depends(oauth2_scheme),
    token_cookie: str = Cookie(None, alias="access_token")
)
  • 优先读取 Bearer Token
  • 如不存在则读取 Cookie
  • 验证失败返回 401

4. 文章 API (routers/posts.py)

4.1 端点列表

方法 路径 说明 鉴权
GET /api/posts 文章列表(含分页/搜索/筛选) 公开
GET /api/posts/{post_id} 单篇文章详情 公开
POST /api/posts 创建文章 管理员
PUT /api/posts/{post_id} 更新文章 管理员
DELETE /api/posts/{post_id} 删除文章 管理员
GET /api/posts/stats/overview 统计概览 管理员
GET /api/posts/slug/{slug} 通过 slug 获取文章 公开
GET /api/posts/category/{category_id} 按分类获取文章 公开
GET /api/posts/tag/{tag_name} 按标签获取文章 公开

4.2 列表查询参数

参数 类型 说明
skip int 分页偏移,默认 0
limit int 每页数量,默认 10
search str 标题/摘要/内容关键词搜索(like %xxx%)
category_id int 按分类筛选
tag str 按标签名筛选
status str published / draft / all,默认 published

4.3 响应数据结构

{
  "items": [
    {
      "id": 1,
      "title": "文章标题",
      "slug": "article-slug",
      "summary": "摘要",
      "cover_image": "/uploads/xxx.jpg",
      "is_published": true,
      "is_draft": false,
      "created_at": "2026-04-28T04:00:00",
      "updated_at": "2026-04-28T04:00:00",
      "view_count": 100,
      "category": {"id": 1, "name": "技术"},
      "tags": [{"id": 1, "name": "Python"}]
    }
  ],
  "total": 50,
  "page": 1,
  "page_size": 10,
  "total_pages": 5
}

4.4 搜索实现

# 标题 > 摘要 > 内容,全文模糊搜索
Post.title.ilike(f"%{search}%") |
Post.summary.ilike(f"%{search}%") |
Post.content.ilike(f"%{search}%")

4.5 浏览次数

  • 每次 GET 单篇文章时 view_count += 1
  • 通过 /api/posts/{id}?increment_view=true 控制

5. 图片上传 (routers/images.py)

5.1 上传接口

POST /api/images/upload
Content-Type: multipart/form-data
Body: file=<二进制文件>

5.2 处理流程

  1. 校验文件类型:白名单 jpg, jpeg, png, gif, webp
  2. 生成唯一文件名:uuid.uuid4().hex + 扩展名
  3. 保存到 backend/uploads/
  4. 返回可访问的 URL:/uploads/{filename}

5.3 图片删除

DELETE /api/images/{filename}
  • 从文件系统删除
  • 同时从所有文章 cover_image 和 content 字段中移除引用

5.4 图片裁剪(封面图)

POST /api/images/{filename}/crop
Body: {"x": 0, "y": 0, "width": 800, "height": 400}
  • 使用 Pillow 裁剪
  • 直接覆盖原图

6. 数据库连接 (database.py)

6.1 配置

# SQLite 本地文件
SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    connect_args={"check_same_thread": False}  # 允许多线程
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

6.2 Session 获取

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
  • 通过 FastAPI Depends 注入到路由处理函数

7. Pydantic 模型 (schemas.py)

Schema 用途
PostCreate 创建文章请求
PostUpdate 更新文章请求
PostResponse 文章响应(含 category/tags)
CategoryCreate 创建分类请求
CategoryResponse 分类响应(含 post_count)
TagCreate 创建标签请求
TagResponse 标签响应
SettingUpdate 更新设置请求
SettingResponse 设置响应
PageCreate 创建页面请求
PageUpdate 更新页面请求
PageResponse 页面响应

8. 错误处理

后端使用 FastAPI 的 HTTPException 统一返回错误:

{
  "detail": "错误描述"
}

常见错误码:

状态码 场景
400 参数错误、数量超限、名称重复
401 未登录/Token 无效
403 无权限
404 资源不存在
422 请求体校验失败

本文档由 nanobot 根据项目源码自动生成。