Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

CaoxuBlog 后端

测试工程师个人技术博客 —— 代码永远在等待一个它从未通过的测试

基于 FastAPI + SQLAlchemy 2.0(async)+ PostgreSQL / SQLite + Redis 的后端服务,配套 Nuxt 3(SSR) 前端(见 caoxublog-web/)。

  • P1:数据模型 + Alembic 迁移 + 博客/教程 CRUD + 标签多对多
  • P2:首页去重、全文搜索、阅读/点赞/评论互动(Redis 去重与频次限制)、敏感词过滤
  • P3:Nuxt 三栏 + 教程三层目录 + Sitemap + SEO(caoxublog-web/)
  • P4:暗黑模式 + 鼠标光点 + Markdown 高亮/行号/复制
  • P5:边界测试套件 + README 自动生成 + CI

技术栈(锁定版本)

端 依赖 版本
后端 fastapi >=0.115,<0.116
后端 uvicorn[standard] >=0.30,<0.35
后端 sqlalchemy[asyncio] >=2.0,<2.1
后端 asyncpg >=0.30,<0.31
后端 aiosqlite >=0.20,<0.22
后端 alembic >=1.13,<1.16
后端 pydantic >=2.7,<3
后端 pydantic-settings >=2.3,<3
后端 redis >=5,<7
后端 pyahocorasick >=2,<3
前端 @nuxtjs/tailwindcss 6.12.2
前端 @playwright/test ^1.48.0
前端 @types/markdown-it ^14.1.2
前端 highlight.js ^11.12.0
前端 markdown-it ^14.1.0
前端 nuxt ^3.13.0
前端 tailwindcss ^3.4.0
前端 typescript ^5.6.0
前端 vue ^3.5.0
前端 vue-router ^4.4.0

快速开始

# 1. 创建虚拟环境并安装依赖
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -e ".[dev]"

# 2. 准备环境变量(可选,默认已用 SQLite)
cp .env.example .env

# 3. 执行数据库迁移
alembic upgrade head

# 4. 灌入演示数据(2 篇博客、1 篇教程、3 个标签)
python scripts/seed.py

# 5. 启动服务
uvicorn app.main:app --reload

启动后访问:

数据库切换(DATABASE_URL)

数据库连接串通过环境变量 DATABASE_URL 切换,支持双后端:

环境 连接串
开发(SQLite,开箱即用) sqlite+aiosqlite:///./caoxublog.db
生产(PostgreSQL 16) postgresql+asyncpg://USER:PASS@HOST:5432/caoxublog
# 切到 PostgreSQL
export DATABASE_URL="postgresql+asyncpg://caoxu:secret@localhost:5432/caoxublog"
alembic upgrade head

说明:主键类型使用 BigInteger().with_variant(Integer, "sqlite"), PostgreSQL 下为 BIGINT(符合 SRS §5.1),SQLite 下退回 INTEGER 以支持自增。

数据表

表名 模型类 说明
comments Comment 评论模型(SRS §5.1 comments 表)。
post_tags - 博客 / 教程与标签的多对多关联表。
posts Post 博客 / 教程文章模型(Post 统一承载 blog 与 tutorial)。
tags Tag 标签模型。
users User 用户模型(预留微信登录,当前不开放自助注册)。

API 一览

方法 路径 说明
GET /api/home 首页:3 篇置顶 + 6 篇最新(不重复)。
GET /api/posts 文章列表(分页 + post_type / status 过滤,软删除不返回)。
POST /api/posts 创建文章(博客 / 教程)。
DELETE /api/posts/{post_id} 软删除:仅置 is_deleted=True,保留数据可恢复。
GET /api/posts/{post_id} 文章详情。
PUT /api/posts/{post_id} 更新文章(部分更新)。
GET /api/posts/{post_id}/comments 评论列表,时间正序(created_at ASC),可选分页。
POST /api/posts/{post_id}/comments 发表评论:先敏感词过滤(422),再频次校验(超限 429)。
POST /api/posts/{post_id}/like 点赞(同一 IP 对同一文章仅计一次,重复点赞不累加)。
POST /api/posts/{post_id}/tags 给文章绑定一批标签(幂等:已绑定的标签不会重复绑定)。
DELETE /api/posts/{post_id}/tags/{tag_id} 解绑文章与单个标签的关联。
POST /api/posts/{post_id}/view 阅读量 +1(同一 IP 对同一文章 24h 内仅计一次)。
GET /api/search 按关键词搜索已发布文章(标题 / 简介 / 正文),支持类型过滤。
GET /api/tags 标签列表(分页)。
POST /api/tags 创建标签(重名返回 409)。
DELETE /api/tags/{tag_id} 删除标签(先解除与文章的关联,再物理删除)。
GET /api/tags/{tag_id} 标签详情。
PUT /api/tags/{tag_id} 更新标签(重名返回 409)。

统一分页响应 Page[T]:{"items": [...], "total": N}。 统一错误响应:{"detail": "..."}(404/409/429)或 {"detail": [...]}(422)。

前端路由(caoxublog-web/)

路径 说明
/ 首页(品牌 + 置顶 + 最新)
/blog 博客列表
/blog/:slug 博客详情(三栏)
/tutorial 教程目录索引
/tutorial/:slug* 教程详情(三栏,catch-all 三层)

Redis 与互动去重

互动接口(阅读 / 点赞 / 评论)依赖 Redis 7.x,Key 设计:

Key 用途 命令
view:{post_id}:{ip} 阅读量 24h 去重 SET ... EX 86400 NX
like:{post_id}:{ip} 点赞去重 SET ... NX
comment:{post_id}:{ip} 评论频次计数 INCR + 过期
# 本地启动 Redis
docker run -d --name caoxublog-redis -p 6379:6379 redis:7
# 或直接使用本机 redis-server

评论次数上限由 COMMENT_LIMIT 控制(默认 3),阅读窗口由 VIEW_DEDUP_SECONDS 控制(默认 86400)。

客户端 IP 提取

get_client_ip 依赖负责解析客户端 IP:

  • 默认(TRUST_PROXY_HEADERS=false):使用直连地址(request.client.host)。
  • 反向代理之后(TRUST_PROXY_HEADERS=true):信任 X-Forwarded-For 首段 / X-Real-IP。

安全提示:仅当应用部署在可信反向代理(Nginx 等)之后才应开启 TRUST_PROXY_HEADERS, 否则攻击者可伪造 X-Forwarded-For 绕过阅读/点赞/评论的频率限制。

敏感词库维护

敏感词库位于 app/data/sensitive_words.txt,每行一个词,# 开头为注释。

  • 使用 pyahocorasick 构建 Aho-Corasick 自动机,多模式匹配性能优于逐词 replace。
  • 词库为进程级懒加载单例:编辑文件后需重启服务才生效(热更新见后续批次)。
  • 命中敏感词的评论直接返回 422,不写入数据库。

运行测试

# 全量
pytest -q

# 覆盖率(要求 ≥ 80%)
pytest --cov=app --cov-report=term-missing

# 重新生成本 README
python scripts/gen_readme.py

工程硬约束(见 AGENTS.md)

  1. 依赖注入必须使用 Annotated[AsyncSession, Depends(get_db)]
  2. async 路由禁止同步 ORM / 同步 Session()
  3. 任何删除操作必须软删除(is_deleted=True)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages